All Articles

モーダルの外側がクリックされたら閉じるパーツを作成

TypeScript の Generics とずうっと格闘しているのですが、React 記事も書いてみます。

よくある仕組みですが、どうやって実現するんだろう?て思っていたら、React でコンポーネントの外側がクリックされか判別するにピンポイントに解決する方法をが書かれていたので自分自身の PJ に導入しました。

自分の環境は TypeScript であり、モーダル毎に閉じるしきい値の判定を都度書くのはスマートではないので、共通化しました。

ソース

  • Component
import React from 'react';
const HomeView: React.FC = () => {
  const closeHandler = () => {
    // 閉じた時の処理
  }
  const { elementRef, closeModal } = useCloseModal(closeHandler);

  return (
    <div className="container">
        <Overlay zIndex={2} onClose={closeModal}>
          <ConfirmModal
            message={'削除しますがよろしいですか。'}
            elRef={elementRef}
            onClick={deleteArticle}
          />
        </Overlay>
    </div>
  );
};

export default HomeView;

閉じた時に実行する処理を useCloseModal に引数として渡します。

オバーレイを閉じる外・内の判定のために、elementRef をオーバーレイコンテンツに設定します。

  • オーバーレイコンテンツ
const ConfirmModal: React.FC<ConfirmModalProps> = ({
  message,
  elRef,
  onClick
}) => {
  return (
    <div ref={elRef}>
      <div>{message}</div>
      <PositiveButton onClick={onClick}>削除</PositiveButton>
    </div>
  );
};

elRef として受け取ったものは、 ref に設定。これで React から ConfirmModal への参照が可能になります。

  • オーバーレイ
import React from 'react';
import styled from 'styled-components';

type OverlayProps = {
  zIndex?: 1 | 2 | 3 | 4 | 5 ;
  onClose: (element: HTMLElement) => void;
};

const Overlay: React.FC<OverlayProps> = ({ zIndex = 1, onClose, children }) => {
  const onClickHandler = (event: React.MouseEvent<HTMLDivElement>) => {
    event.preventDefault();
    if (onClose && event.target instanceof HTMLElement) {
      onClose(event.target);
    }
  };
  return (
    <OverlayDiv zIndex={zIndex} onClick={onClickHandler}>
      {children}
    </OverlayDiv>
  );
};

export default Overlay;

const OverlayDiv = styled.div`
  display: block;
  position: fixed;
  top: 0;
  bottom: 0;
  right: 0;
  left: 0;
  z-index: ${props => props.zIndex};
  background: rgba(0, 0, 0, 0.5);
  overflow-y: auto;
`;

onClose は関数なので、クリックされると、Overlay に処理が引き渡されて closeAction が実行されます。

  • 画面の閉じる判定部分の共通化
import { useRef } from 'react';

// elementRefの外側の領域をタップしたときに、closeActionを実行
export const useCloseModal = (closeAction: () => void) => {
  const elementRef = useRef<HTMLElement>(null);
  const closeModal = (element: HTMLElement) => {
    if (elementRef.current && !elementRef.current.contains(element)) {
      closeAction();
    }
  };

  return { elementRef, closeModal };
};

クリックされた場所が、elementRef の外側の場合だけ closeAction()を実行します。

仕組み

  • オーバーレイは、表示するコンテンツと、閉じるアクションを受け取ります。
  • useRef にオーバーレイのコンテンツ部分の ref に設定して、React から参照出来るようになります。
  • useCloseModal 内部で、クリックした位置が useRef に設定したエリアの外または内かを判定して、エリア外をクリックした時のみ closeAction を実行します。  ← ここの処理はおまじないになる部分のため意識しないようにしました。

参考