ReactでModal作成


Reactを使用してModal作成したっていう話

結論

今回はライブラリを使用するぜっていうことに落ち着いた。

そこに至るまでの経緯

1.hooks & Portalを使用

Portalとは

ポータル (portal) は、親コンポーネントの DOM 階層外にある DOM ノードに対して子コンポーネントをレンダーするための公式の仕組みを提供します。(引用:https://ja.reactjs.org/docs/portals.html)

実装方法

index.htmlにmodal用DOMを設置

index.html
<!doctype html>
  <html lang="ja">
  <head>
    <title>modal test</title>
    <meta charset="UTF-8">
    <meta name="format-detection" content="telephone=no">
    <meta name="keywords" content="~~"><meta name="description" content="~~">
    <meta name="viewport" content="width=device-width">
    <link rel="icon" href="/favicon.png">
  </head>
  <body>
    <main role="main">
      <div id="app"></div>
      <div id="modal"></div>
    </main>
    <script src="/js/main.js"></script>
  </body>
</html>

id="modal"以下にレンダリングさせるコンポーネントを作成

ModalPortal.jsx
import ReactDOM from 'react-dom';

export const ModalPortal = ({ children }) => {
  // index.html内にあるid=modalの要素を取得
  const el = document.getElementById('modal');
 // children = modal を el = modal要素 へ レンダー
  return ReactDOM.createPortal(children, el);
};

これでmodalを表示する準備は完了あとはmodalの内容のコンポーネントを用意
以下のコンポーネントがModalPortalでのchildrenに値します

Modal.jsx
export const Modal = () => {
  return (
    <div>
      <h3>Modalコンポーネント</h3>
      <button>閉じる</button>
    </div>
  );
};

そして

App.jsx
import ReactDOM from 'react-dom';
import Modal from "./Modal";
import ModalPortal from "./ModalPortal";
import 'react-hot-loader'


const app = document.getElementById('app')
const App = () => {
  return (
    <div>
      <h1>Modal実装したった</h1>
      <ModalPortal>
        <Modal />
      </ModalPortal>
    </div>
  )
}

ReactDOM.render(<App />,app)

それからuseState, useRefでの処理を加えてModalぽい動きを実装します

useRefとuseStateの処理について軽く説明

useStateでボタンを押した際に、モーダルを表示するかのstateを用意し、表示非表示を行っています。
useRefでは、modalの外側をクリックした時に、非表示を行うためにクリックされた要素が外側の要素か判定させるために用意しています。(手抜きですみません。)

なぜやめたか

modal内からmodal外へのonClickでも発火してしまう。

先ほどuseRefで外側をクリックされた場合モーダルを非表示にさせるとのことだったのですが、内側から外側にかけてのクリックでも発火してしまう。

考えうる対策方法

onClickではなくmousedownとtouchstartで要素を見分ける。mousedownとtouchstart二つ必要な理由は、PCとSPでイベント発火が違うので二つ用意する必要があります。
ですが、スクロールの時も発火するではないかなど不安要素があったため、時間がかかりそうと感じ断念しました

2.react-modalを使用

実装時間も限られているのでライブラリでやっちゃおう



実装内容

画像の通りinstallから行ってなっていく

// npm
$ npm install --save react-modal
// yarn
$ yarn add react-modal
import React, { useState } from 'react'
import { jsx } from '@emotion/react'
import Modal from 'react-modal'
export const useModal = () => {
  const [modal, setModal] = useState(false)
  const modalOpen = () => { setModal(true) return }
  const modalClose = () => { setModal(false) }
  return { modal, modalOpen, modalClose }
}
export const ModalBox = (props) => {
  Modal.setAppElement('#modal')
  const customStyles = {
    overlay: { position: 'fixed', top: 0, width: '100%', height: '100%'},
    content: { position: 'static', maxWidth: props.maxWidth}
  };
  const modalClose = () => { props.close() }
  return (
    <Modal isOpen={props.modal} onRequestClose={modalClose} style={customStyles}>
      {props.children}
    </Modal>
  );
};
  • useModalでModal制御
  • Modal.setAppElementでモーダルを表示するDOMを設定
  • customStyles
    • overlay: Modal外の設定
    • content: Modalの中身の設定
  • あとは表示するだけでさっきの問題が解決

余談

Modalを作成し終えた後に見つけた便利なhookを紹介したいと思います。
先ほどの外側のonClick問題を解消したものになります。