Reactでモーダルを作る
最近Reactを使っているのでReactのモーダルメモ。
モーダルを開いている時はbodyのスクロールができないようになります。
モーダルを起動するボタン(ModalBtn)と表示するモーダル(Modal)が分かれているので、AppでUseState
をセットしてそれぞれボタンとモーダルに渡しています。
AppとModalBtnの値の引き渡し
子→親へ値を返す
子から親へ値を返すにはReactはVueのように$emit()
のような便利なものはないので、親コンポーネントで実行する関数を作り、子のクリックイベントで発火させます。
export default function App() {
const [isModalOpen, setIsModalOpen] = useState(false);//モーダルの表示状態
const [modalSelect, setModalSelect] = useState("");//モーダルの中身の振り分け
const [modalScroll, setModalScroll] = useState(0);//モーダルを開いた時のスクロール値を取っておく
//モーダルを開く
const modalOpen = (taregt: string) => {
setIsModalOpen(true);//モーダル状態をtrueに
setModalSelect(taregt);//モーダルの中身で何が選ばれたか
setModalScroll(window.scrollY);
let body = document.getElementsByTagName("body");
body[0].style.top = -window.scrollY + "px";
body[0].classList.add("is-fixed");
};
//中略
return (
//中略
//関数modalOpenを子コンポーネントに渡す
<ModalBtn title="モーダル1" onClick={() => modalOpen("modal-1")} />
//中略
);
}
import React from "react";
type Props = {
title: string;
onClick: any;
};
export const ModalBtn: React.VFC<Props> = (props) => {
//クリックイベントで受け取ったonClickを発火させる
return <button onClick={props.onClick}>{props.title}</button>;
};
export default ModalBtn;
そのまま入れると無限ループになる
ちなみにボタンを下記のようにするとToo many re-renders. React limits the number of renders to prevent an infinite loop.と警告が出ます。私も数時間ハマっていました。
関数modalOpen
の中でuseState
を使用しており、useState
はrender時に呼ばれるので無限ループに陥ってしまいます。そのため関数の中に入れて、クリックイベントが呼ばれた時だけ実行させます。
<div className="modal-button-wrap">
//↓Too many re-renders. React limits the number of renders to prevent an infinite loop.
<ModalBtn title="モーダル1" onClick={modalOpen("modal-1")} />
//関数に入れて子コンポーネントに渡す
<ModalBtn title="モーダル1" onClick={() => modalOpen("modal-1")} />
</div>
AppとModalの値の引き渡し
ModalBtn
のクリックイベントでuseState
を変えたら、その情報をModal
に渡します。
export default function App() {
const [isModalOpen, setIsModalOpen] = useState(false);//モーダルの表示状態
const [modalSelect, setModalSelect] = useState("");//モーダルの中身の振り分け
const [modalScroll, setModalScroll] = useState(0);//モーダルを開いた時のスクロール値を取っておく
//モーダルを開く
const modalOpen = (taregt: string) => {
setIsModalOpen(true);
setModalSelect(taregt);
setModalScroll(window.scrollY);
let body = document.getElementsByTagName("body");
body[0].style.top = -window.scrollY + "px";
body[0].classList.add("is-fixed");
};
//モーダルを閉じる
const modalClose = () => {
setIsModalOpen(false);//モーダル表示状態をfalseに
let body = document.getElementsByTagName("body");
body[0].style.top = "0px";
body[0].classList.remove("is-fixed");
window.scrollTo(0, modalScroll);
setModalScroll(0);
};
return (
//中略
<Modal
modalStatus={isModalOpen}
modalContent={modalSelect}
onClick={modalClose}
/>
//中略
);
}
ここでは孫コンポーネントのModalContent
へさらに値を渡したりしています。
import React from "react";
import { IconContext } from "react-icons";//バツのアイコンを出すのに読ませている
import { MdClose } from "react-icons/md";//バツのアイコンを出すのに読ませている
import ModalContent from "../modules/ModalContent";
type Props = {
modalStatus: boolean;
modalContent: string;
onClick: any;
};
export const Modal: React.VFC<Props> = (props) => {
//モダールを閉じる
const modalClose = () => {
//親コンポーネント(App)のmodalCloseを発火させる。
props.onClick();
};
return (
//props.modalStatusの値によってモーダルを出し分け
<div className={props.modalStatus ? "modal is-active" : "modal"}>
<div className="modal-inner">
//クリックでmodalCloseを発火させる
<div className="modal-close" onClick={modalClose}>
<IconContext.Provider value={{ size: "20px" }}>
<MdClose />
</IconContext.Provider>
</div>
<div className="modal-content">
//ModalContentに受け取った props.modalContent をそのまま渡す。
<ModalContent modalId={props.modalContent} />
</div>
</div>
</div>
);
};
export default Modal;
モーダルの中身の振り分け
別で用意したGetModalContents.ts
にモーダル情報の入っている配列を配置し、受け取ったmodalId
をもとに配列をfilter
で抜き出します。
import React from "react";
import { GetModalContents } from "../../hooks/GetModalContents";
type Props = {
modalId: string;
};
//モーダルの中身
export const ModalContent: React.VFC<Props> = (props) => {
//親のModalから受け取ったmodalIdをもとに中身を選ぶ。GetModalContentsにidを渡す。
const modalContent = GetModalContents(props.modalId);
// ↓こんな感じで値が返ってくる
//modalContent = [{
// id: "modal-1",
// title: "モーダル1です",
// content: "モダール1のコンテンツ"
// }]
return (
<>
{0 < modalContent.length ? (
<>
<p className="modal-title">{modalContent[0].title}</p>
<div className="modal-text">{modalContent[0].content}</div>
</>
) : (
<></>
)}
</>
);
};
export default ModalContent;
const modalContents = [
{
id: "modal-1",
title: "モーダル1です",
content: "モダール1のコンテンツ"
},
{
id: "modal-2",
title: "モーダル2です",
content: "モダール2のコンテンツ"
},
{
id: "modal-3",
title: "モーダル3です",
content: "モダール3のコンテンツ"
}
];
export const GetModalContents = (id: string) => {
//渡されたidをもとにモーダルの中身を選んで返す
const modalContent = modalContents.filter((x) => x.id === id);
return modalContent;
};