Help us understand the problem. What is going on with this article?

【React】Portal+Context+Hooks でモーダルダイアログを簡単に扱う

Portal とは

ReactDOM.createPortal()は、コンポーネント階層の外側(=#root と同じ階層)の DOM 要素にコンポーネントを描画するメソッドです。
これによってコンポーネント階層内での z-index を気にすることなくモーダルダイアログを表示することが可能になります。
開閉の制御がバケツリレーになることを避けるためと、記述を簡潔にするために Context と Hooks を利用してできるだけ簡単に扱えるモーダルダイアログを実装してみます。
(わかりやすい見た目にするために SemanticUI を使用しているので実際に実行する場合は <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css"/> を追加してください)

実装

ルートノード

モーダル用のルートノードを追加します。

index.html
<!-- 省略 -->
<div id="modal"></div>
<!-- 省略 -->

Context

useState()で使える形で Context を定義します。

isModalShown.js
import React from 'react';

export default React.createContext({
  isModalShown: false, // モーダル開閉ステータス
  toggleModalShown: () => {}, // ステータス更新関数
});

モーダルダイアログ本体

Hooks を使わない場合と比べるとだいぶすっきり書けます。
タイトルやテキストは props で渡すべきですが今回は省略します。

Modal.jsx
import React, { useContext } from 'react';
import ReactDOM from 'react-dom';
import isModalShown from '../contexts/isModalShown';

const Modal = () => {
  // 背景とCloseボタンのクリックで開閉ステータスをfalseにする
  const renderModal = modalHookState => {
    const [isModalShown, changeShownModal] = modalHookState;
    return (
      <div
        onClick={() => changeShownModal(false)}
        className="ui dimmer modals visible active"
      >
        <div
          onClick={e => e.stopPropagation()}
          className="ui standard modal visible active"
        >
          <div className="ui header">Modal Dialog</div>
          <div className="content">This is modal dialog with Context</div>
          <div className="actions">
            <button
              className="ui button primary"
              onClick={() => changeShownModal(false)}
            >
              Close
            </button>
          </div>
        </div>
      </div>
    );
  };

  // Contextの値を取得して開閉制御
  const modalHookState = useContext(isModalShown);
  return ReactDOM.createPortal(
    modalHookState[0] && renderModal(modalHookState),
    document.querySelector('#modal'),
  );
};

export default Modal;


モーダルを呼ぶ側のコンポーネント

モーダル開閉のステータスを更新する関数をonClickにバインドします。

CallModal.jsx
import React, { useContext } from 'react';
import isModalShown from '../contexts/isModalShown';
import Modal from './Modal';

const CallModal = () => {
  // Open Modalボタンクリックで開閉ステータスをtrueにする
  const renderCallModal = changeShownModal => {
    return (
      <div>
        <button onClick={() => changeShownModal(true)}>Open Modal</button>
      </div>
    );
  };

  return (
    <div>
      {renderCallModal(useContext(isModalShown)[1])}
      <Modal />
    </div>
  );
};

export default CallModal;


Provider

モーダル開閉のステータスと、変更するための関数を Provider で渡します。

App.jsx
import React from 'react';
import isModalShown from '../contexts/isModalShown';
import CallModal from './CallModal';

const App = () => {
  // modalState...[isModalShown, toggleModalShown]
  const modalState = React.useState(false);
  return (
    <isModalShown.Provider value={modalState}>
      <CallModal />
    </isModalShown.Provider>
  );
};

export default App;


結果

画面収録 2019-04-01 20.37.38.mov.gif
バケツリレーを避けることで、Modalまでのコンポーネントが深くネストした時の再利用性や独立性を高めることができました。
表示/非表示を切り替えるときは必ずContextの値を変更すればいいので、扱いやすくもあると思います。

参考URL

https://reactjs.org/docs/context.html
https://reactjs.org/docs/hooks-reference.html

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away