React ポータル

概要: このチュートリアルでは、React ポータルを使用して、親コンポーネントの DOM 階層の外にある DOM ノードに子要素をレンダリングする方法を学習します。

React ポータルの紹介

React ポータルは、親コンポーネントの DOM 階層の外にあるDOMノードに子要素をレンダリングする方法を提供します。

React ポータルは、モーダル、ツールチップ、ドロップダウンなど、通常の DOM 階層の外側にコンポーネントをレンダリングする場合に役立ちます。

ポータルを作成するには、ReactDOM.createPortal() 関数を使用します。createPortal 関数は2つの引数を取ります。

  • レンダリングする JSX コンテンツ。
  • JSX コンテンツをレンダリングする DOM ノード。

React ポータルを実演するために、React で再利用可能なモーダルコンポーネントを作成します。

新しい React プロジェクトの作成

まず、ターミナルを開き、次のコマンドを実行して新しい React プロジェクトを作成します。

npx create-react-app react-portalsCode language: JavaScript (javascript)

次に、react-portals ディレクトリに移動します。

cd react-portalsCode language: JavaScript (javascript)

次に、次のコマンドを実行して React アプリを起動します。

npm startCode language: JavaScript (javascript)

src ディレクトリ内のすべてのファイルを削除し、最初から始めます。

モーダルの最初のバージョンを作成する

ステップ1. 画面にAppコンポーネントを表示するindex.jsファイルを作成します。

import ReactDOM from 'react-dom/client';
import App from './App';

const el = document.querySelector('#root');
const root = ReactDOM.createRoot(el);

root.render(<App />);Code language: JavaScript (javascript)

ステップ2. Appコンポーネントを作成します。

import { useState } from 'react';

const App = () => {
  return (
    <div className="container">
       Modal
    </div>
  );
};

export default App;Code language: JavaScript (javascript)

ステップ3. Modalコンポーネントを作成します。

import './Modal.css';

const Modal = () => {
  return (
    <div className="modal-container"> 
        Modal
    </div>
  );
};

export default Modal;Code language: JavaScript (javascript)

ステップ4. 空のModal.cssファイルを作成します。

ステップ5. モーダルを表示するようにAppコンポーネントを拡張します。

import { useState } from 'react';
import Modal from './Modal';

const App = () => {
  const [showModal, setShowModal] = useState(false);

  const handleClick = () => setShowModal(!showModal);

  return (
    <div className="container">
      <button type="button" onClick={handleClick}>
        Open
      </button>
      {showModal && <Modal />}
    </div>
  );
};

export default App;Code language: JavaScript (javascript)

Appコンポーネントは、showModal状態を使用してモーダルを表示/非表示にします。showModalがtrueの場合、Modalコンポーネントが表示され、falseの場合、Modalコンポーネントは表示されません。

Appコンポーネントには、クリックするとモーダルを開くボタンもあります。クリックイベントが発生すると、モーダルを表示するために、showModal状態の値をfalseからtrueに切り替えます。

ステップ6. モーダルにコンテンツを表示します。

import './Modal.css';

const Modal = () => {
  return (
    <div className="modal-container">
      <div className="modal">
        <h2>Modal is cool </h2>
        <p>This is a modal in React</p>
      </div>
    </div>
  );
};

export default Modal;Code language: JavaScript (javascript)

ステップ7. Modal.cssファイルにスタイルを追加します。

.modal-container {
  /* create an overlay */
  position: absolute;
  inset: 0;
  background-color: rgba(0, 0, 0, 0.5);

  /* center the modal */
  display: flex;
  justify-content: center;
  align-items: center;
}

.modal {
  background-color: white;
  padding: 1rem;
  border-radius: 0.5rem;
}Code language: JavaScript (javascript)

ボタンをクリックすると、モーダルが表示されます。

React Portals - Modal

これは、.modal-container要素でabsolute位置を使用し、すべての親要素がstatic以外の位置を持っていないため機能します。

.container要素のpositionModal.cssファイルでrelativeに設定されていると、モーダルは正しく機能しません。

.container {
  position: relative;
}

/* other rules */Code language: JavaScript (javascript)
React Portals - Broken Modal

この問題を解決するには、.container要素ではなくbody要素の下にモーダルをレンダリングする必要があります。そのためには、React ポータルを使用します。

React ポータルの使用

ステップ1. public/index.htmlファイルのbody要素の直下に、クラス.model-wrapperを持つdivを追加します。

...
 <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>

    <div class="modal-wrapper"></div>
  </body>
...Code language: JavaScript (javascript)

ステップ2. React ポータルを使用してモーダルをレンダリングします。

.modal-wrapper要素内にモーダルコンポーネントをレンダリングするには、ReactDOM.createPortal()関数を使用します。

import ReactDOM from 'react-dom';
import './Modal.css';

const Modal = () => {
  return ReactDOM.createPortal(
   <div className="modal-container">
      <div className="modal">
        <h2>Modal is cool </h2>
        <p>This is a modal in React</p>
      </div>
    </div>,
    document.querySelector('.modal-wrapper')
  );
};

export default Modal;Code language: JavaScript (javascript)

開くボタンをクリックすると、モーダルが正しく表示されます。

これまでのところ、モーダルには静的なコンテンツしかありません。通常、モーダルには親コンポーネントによって提供される動的なコンテンツが含まれており、それ自体を閉じるボタンがあります。これらの機能をModalコンポーネントに追加するには、propsを使用できます。

ステップ3. Appコンポーネントを拡張します。

import { useState } from 'react';
import Modal from './Modal';

const App = () => {
  const [showModal, setShowModal] = useState(false);

  const handleClick = () => setShowModal(!showModal);
  const handleClose = () => setShowModal(false);

  const actions = (
    <button type="button" onClick={handleClose}>
      Close
    </button>
  );

  const modal = (
    <Modal onClose={handleClose} actions={actions}>
      <p>This is a modal!</p>
    </Modal>
  );

  return (
    <div className="container">
      <button type="button" onClick={handleClick}>
        Open
      </button>

      {showModal && modal}
    </div>
  );
};

export default App;Code language: JavaScript (javascript)

仕組み

まず、onCloseactionsプロップをModalコンポーネントに追加します。

<Modal onClose={handleClose} actions={actions}>Code language: JavaScript (javascript)

次に、開始タグと終了タグの間に配置することで、子コンポーネントをModalコンポーネントに提供します。

<Modal onClose={handleClose} actions={actions}>
    <p>This is a modal!</p>
</Modal>Code language: JavaScript (javascript)

<Modal>タグと</Modal>タグの間に任意のコンテンツを配置できることに注意してください。Modalコンポーネントでは、それらはchildrenプロップに格納されます。

次に、閉じるボタンを含むモーダルのactionsを定義し、handleCloseイベントハンドラをクリックイベントに登録します。

const actions = (
    <button type="button" onClick={handleClose}>
      Close
    </button>
);Code language: JavaScript (javascript)

最後に、handleClose関数でshowModal状態をfalseに設定してモーダルを閉じます。

const handleClose = () => setShowModal(false);Code language: JavaScript (javascript)

ステップ4. Modalコンポーネントにpropsを追加します。

onClosechildrenactionsプロップをModalコンポーネントに追加します。

import ReactDOM from 'react-dom';
import './Modal.css';

const Modal = ({ onClose, children, actions }) => {
  return ReactDOM.createPortal(
    <div className="modal-container" onClick={onClose}>
      <div className="modal">
        {children}
        {actions}
      </div>
    </div>,
    document.querySelector('.modal-wrapper')
  );
};

export default Modal;Code language: JavaScript (javascript)

Modalコンポーネント内では

  • オーバーレイがクリックされたときにモーダルを閉じるために、.modal-container要素のクリックイベントにonClose関数を登録します。
  • childrenとactionsプロップをレンダリングします。

「開く」ボタンをクリックすると、モーダルが表示されます。「閉じる」ボタンまたはオーバーレイをクリックすると、モーダルが閉じます。

しかし、モーダルの内部をクリックすると、これも閉じます。これは予期しない動作です。その理由は、クリックイベントが.modal-containerの子要素から.modal-container要素に伝播されるためです。

子要素から親要素へのイベントの伝播を停止するには、イベントオブジェクトのstopPropagation()メソッドを使用できます。

import ReactDOM from 'react-dom';
import './Modal.css';

const Modal = ({ onClose, children, actions }) => {

  const handleClick = (e) => e.stopPropagation();

  return ReactDOM.createPortal(
    <div className="modal-container" onClick={onClose}>
      <div className="modal" onClick={handleClick}>
        {children}
        {actions}
      </div>
    </div>,
    document.querySelector('.modal-wrapper')
  );
};

export default Modal;Code language: JavaScript (javascript)

仕組み

まず、.modal要素にonClickプロップを追加します。

<div className="modal" onClick={handleClick}>Code language: JavaScript (javascript)

次に、handeClick関数でイベントオブジェクトのstopPropagation()メソッドを呼び出すことによって、イベントの伝播を停止します。

const handleClick = (e) => e.stopPropagation();Code language: JavaScript (javascript)

これで、「閉じる」ボタンを除いて、モーダルの内部をクリックしてもモーダルは閉じなくなります。

スクロールの問題の修正

Appコンポーネントに長いコンテンツがあり、スクロールすると、オーバーレイが画面全体を覆いません。

React Portals - Scroll

これを修正するには、.modal-containerpositionabsoluteからfixedに変更できます。

.modal-container {
  /* create an overlay */
  position: absolute;
  inset: 0;
  background-color: rgba(0, 0, 0, 0.5);

  /* center the modal */
  display: flex;
  justify-content: center;
  align-items: center;
}Code language: CSS (css)

React モーダルプロジェクトのソースコードのダウンロード

React モーダルプロジェクトのソースコードをダウンロードしてください。

まとめ

  • React ポータルを使用して、親コンポーネントの DOM 階層の外にある DOM ノードに子要素をレンダリングします。
このチュートリアルは役に立ちましたか?