使い易いモーダルウィンドウのコンポーネントを作成します。
useModalフックを作成する
まずは、Modalコンポーネントのロジック部分であるカスタムフックを作成します。名前はuseModalとします。
import React, { useState, useEffect } from "react";
const useModal = (initialShow: boolean): [boolean, () => void] => {
const [show, setShow] = useState(initialShow);
const closeModal = () => setShow(false);
useEffect(() => {
setShow(initialShow);
}, [initialShow]);
return [show, closeModal];
};
export default useModal;
useModalフックは、引数としてinitialShowを受け取り、showとcloseModalを返します。
initialShowは、モーダルウィンドウの初期状態を示す真偽値です。trueが指定されると初めから表示され、falseが指定されると初めは非表示となるようにします。
そしてinitialShowはshowステートの初期値に設定します。
また、useEffectフックを用いて、initialShowが変化したときにその都度
showは、モーダルウィンドウの現在の表示・非表示の状態を示すステートであり、この値の変化によってモーダルウィンドウの表示・非表示が切り替わるようにします。
closeModal関数はモーダルウィンドウを非表示にする関数であり、setShowによってshowをfalseに切り替えます。
Modalコンポーネントを作成する
次に、Modalコンポーネントを以下のように作成します。
import React from "react";
import Button from "./Button";
import useModal from "../hooks/useModal";
type ModalComponentProps = {
className?: string;
initialShow?: boolean;
showCloseButton?: boolean;
closableOverlay?: boolean;
};
const StyledModal = styled.div`
background-color: #fff;
border-radius: .4em;
display: flex;
justify-content: center;
align-items: center;
padding: 1em;
position: relative;
min-height: 14em;
min-width: 20em;
& .close-button {
border-radius: .7em;
display: block;
height: 1.4em;
padding: 0;
position: absolute;
top: -.5em;
right: -.5em;
width: 1.4em;
}
`;
const Overlay = styled.div`
background-color: rgba(0, 0, 0, .5);
display: flex;
align-items: center;
justify-content: center;
height: 100%;
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 2000;
`;
const ModalContent = styled.p`
text-align: center;
`;
const Modal = ({ className, initialShow=true, showCloseButton=true, closableOverlay=true, children }: ModalComponentProps) => {
const [show, closeModal] = useModal(initialShow);
return show ? (
<Overlay {...(closableOverlay && {onClick: closeModal})}>
<StyledModal className={className} onClick={e => e.stopPropagation()}>
<ModalContent>{children}</ModalContent>
{showCloseButton && <Button
className="close-button"
onClick={closeModal}
>×</Button>}
</StyledModal>
</Overlay>
) : null;
};
export default Modal;
まずModalコンポーネントは、propsとして初期状態の表示、非表示を示すinitialShow、閉じるボタンの表示、非表示を表すshowCloseButton、そしてモーダルウィンドウのコンテンツとなるchildrenを受け取るようにしています。
次に、コンポーネント内で先ほど作成したuseModalフックを呼び出します。その際、propsで受け取ったinitialShowを引数として渡します。
Modalコンポーネントが返すReact要素は、show(=initialShow)がtrueであるときに表示されるようにします。そのため、React要素を返す際は三項演算子を用いてshowがtrueであればReact要素を返し、falseであればnullを返して何も表示されないようにしています。
showがtrueのときに返すReact要素は、モーダルウィンドウを構成する各コンポーネントOverlay、StyledModal、ModalContent、Buttonで構成されています。
Overlayコンポーネントはモーダルウィンドウのオーバーレイを表示するためのコンポーネントです。
StyledModalはモーダル部分を表すコンポーネントであり、ModalContentとButtonで構成されています。
ModalContentはモーダルのコンテンツ部分を表すコンポーネントであり、propsとして受け取ったchildrenを子要素に持たせています。
Buttonはボタンを表すコンポーネントであり、ここではモーダルウィンドウを閉じるボタンとして使用しています。 ※Buttonコンポーネントについてはを参照してください。
モーダルウィンドウを閉じるボタンは、デフォルトではモーダルウィンドウの右上にバツ印で表示されるようにしていますが、中には「右上には表示したくない!」という方や、「コンテンツ内に表示したい!」という方、そして「バツ印ではなく「close」のような独自のボタン名を表示したいという方もおられるのでは?と考え、右上の閉じるボタンについては論理積演算子&&を利用してshowCloseButtonにtrueが指定されたときのみ表示されるようにしています。
また、モーダルウィンドウは通常、閉じるボタンをクリックする以外にもオーバーレイをクリックしたときにも消える仕組みになっています。 しかし、年齢確認画面のような「はい」か「いいえ」のどちらかを選択しなければ消えないような画面を作る場合は、それでは困ります。 そこで、今回作成するModalコンポーネントでは、closableOverlayがtrueのときのみonClick属性にcloseModalが指定され、オーバーレイのクリックによるモーダルウィンドウのクローズが有効となるようにしています。
ですが、それだけではいけません。何故なら、closableOverlayにtrueが指定され、オーバーレイにイベントハンドラが追加された場合、オーバーレイの子要素であるコンテンツがクリックされたときにclickイベントが発生しますが、そのイベントはバブリングによって親要素のオーバーレイにまで伝わることによって、オーバーレイに登録されているイベントリスナーcloseModalが反応してしまうからです。
そうなると、オーバーレイをクリックしていないにも関わらず、モーダルウィンドウが消えてしまうのです。
これを防ぐために、StyledModalコンポーネントのclick属性にe => e.stopPropagation()
を指定して、中身のコンテンツがクリックされたときにclickイベントが親であるオーバーレイに伝わらないようにしています。
Modalコンポーネントの使用例
以下は、作成したModalコンポーネントをAppコンポーネント内で使用した例です。
import React from "react";
import Modal from "./components/Modal";
const App = () => {
return (
<div>
<Modal className="">
<h1>Title</h1>
<p>Text</p>
</Modal>
</div>
);
};