createPortalを使うと、子要素をDOMの異なる部分にレンダリングできます。

<div>
<SomeComponent />
{createPortal(children, domNode, key?)}
</div>

リファレンス

createPortal(children, domNode, key?)

ポータルを作成するには、createPortalを呼び出し、JSXと、それをレンダリングするDOMノードを渡します。

import { createPortal } from 'react-dom';

// ...

<div>
<p>This child is placed in the parent div.</p>
{createPortal(
<p>This child is placed in the document body.</p>,
document.body
)}
</div>

以下の例をご覧ください。

ポータルは、DOMノードの物理的な配置のみを変更します。それ以外の点では、ポータルにレンダリングするJSXは、それをレンダリングするReactコンポーネントの子ノードとして機能します。たとえば、子要素は親ツリーによって提供されるコンテキストにアクセスでき、イベントはReactツリーに従って子から親へバブルアップします。

パラメータ

  • children: Reactでレンダリングできるもの。JSX(例:<div /> または <SomeComponent />)、フラグメント<>...</>)、文字列、数値、またはこれらの配列など。

  • domNode: document.getElementById()によって返されるようなDOMノード。ノードは既に存在している必要があります。更新中に異なるDOMノードを渡すと、ポータルコンテンツが再作成されます。

  • オプション key: ポータルのキーとして使用される一意の文字列または数値。

戻り値

createPortal は、JSX に含めたり、React コンポーネントから返したりできる React ノードを返します。React がレンダー出力でこのノードを見つけると、提供された children を、提供された domNode の中に配置します。

注意点

  • ポータルからのイベントは、DOM ツリーではなく React ツリーに従って伝播します。たとえば、ポータル内をクリックし、そのポータルが <div onClick> でラップされている場合、その onClick ハンドラーが起動します。これが問題を引き起こす場合は、ポータル内からのイベントの伝播を停止するか、ポータル自体を React ツリーの上位に移動してください。

使用方法 (略:SVG アイコン)

DOM の別の部分へのレンダリング (略:SVG アイコン)

*ポータル* を使用すると、コンポーネントの子要素の一部を DOM の別の場所にレンダリングできます。これにより、コンポーネントの一部が、それが含まれているコンテナから「エスケープ」できます。たとえば、コンポーネントは、ページの残りの部分の上または外側に表示されるモーダルダイアログまたはツールチップを表示できます。

ポータルを作成するには、createPortal の結果を、JSXそれが配置されるべき DOM ノード を使用してレンダリングします。

import { createPortal } from 'react-dom';

function MyComponent() {
return (
<div style={{ border: '2px solid black' }}>
<p>This child is placed in the parent div.</p>
{createPortal(
<p>This child is placed in the document body.</p>,
document.body
)}
</div>
);
}

React は、渡した JSX の DOM ノードを、提供した DOM ノード の中に配置します。

ポータルがない場合、2 番目の <p> は親の <div> の中に配置されますが、ポータルはそれを document.body: に「テレポート」します。

import { createPortal } from 'react-dom';

export default function MyComponent() {
  return (
    <div style={{ border: '2px solid black' }}>
      <p>This child is placed in the parent div.</p>
      {createPortal(
        <p>This child is placed in the document body.</p>,
        document.body
      )}
    </div>
  );
}

2 番目の段落が、境界線を持つ親 <div> の外側に視覚的にどのように表示されるかに注目してください。開発ツールで DOM 構造を調べると、2 番目の <p><body> に直接配置されていることがわかります。

<body>
<div id="root">
...
<div style="border: 2px solid black">
<p>This child is placed inside the parent div.</p>
</div>
...
</div>
<p>This child is placed in the document body.</p>
</body>

ポータルは、DOM ノードの物理的な配置のみを変更します。他のすべての点で、ポータルにレンダリングする JSX は、それをレンダリングする React コンポーネントの子ノードとして機能します。たとえば、子は親ツリーによって提供されるコンテキストにアクセスでき、イベントは依然として React ツリーに従って子から親へバブルアップします。


ポータルを使用したモーダルダイアログのレンダリング (略:SVG アイコン)

ダイアログを呼び出すコンポーネントが overflow: hidden やその他のダイアログに干渉するスタイルを持つコンテナ内にある場合でも、ポータルを使用してページの残りの部分の上に浮かぶモーダルダイアログを作成できます。

この例では、2 つのコンテナにはモーダルダイアログを妨害するスタイルがありますが、ポータルにレンダリングされたコンテナは影響を受けません。これは、DOM ではモーダルが親 JSX 要素内に含まれていないためです。

import NoPortalExample from './NoPortalExample';
import PortalExample from './PortalExample';

export default function App() {
  return (
    <>
      <div className="clipping-container">
        <NoPortalExample  />
      </div>
      <div className="clipping-container">
        <PortalExample />
      </div>
    </>
  );
}

落とし穴

ポータルを使用する場合、アプリがアクセス可能であることを確認することが重要です。たとえば、ユーザーが自然な方法でポータル内外にフォーカスを移動できるように、キーボードフォーカスを管理する必要がある場合があります。

モーダルを作成する場合は、WAI-ARIA モーダルオーサリングプラクティス に従ってください。コミュニティパッケージを使用する場合は、アクセス可能であり、これらのガイドラインに従っていることを確認してください。


React コンポーネントを React 以外のサーバーマークアップにレンダリングする (略:SVG アイコン)

React ルートが、React で構築されていない静的またはサーバーレンダリングされたページの一部である場合、ポータルは役立ちます。たとえば、ページが Rails のようなサーバーフレームワークで構築されている場合、サイドバーなどの静的領域内にインタラクティブな領域を作成できます。複数の個別の React ルートを持つよりも、ポータルを使用すると、アプリを、その部分が DOM の異なる部分にレンダリングされても、共有状態を持つ単一の React ツリーとして扱うことができます。

import { createPortal } from 'react-dom';

const sidebarContentEl = document.getElementById('sidebar-content');

export default function App() {
  return (
    <>
      <MainContent />
      {createPortal(
        <SidebarContent />,
        sidebarContentEl
      )}
    </>
  );
}

function MainContent() {
  return <p>This part is rendered by React</p>;
}

function SidebarContent() {
  return <p>This part is also rendered by React!</p>;
}


React コンポーネントを React 以外の DOM ノードにレンダリングする (略:SVG アイコン)

React の外部で管理されている DOM ノードのコンテンツを管理するために、ポータルを使用することもできます。たとえば、React 以外のマップウィジェットと統合していて、ポップアップ内に React コンテンツをレンダリングする場合を考えてみましょう。これを行うには、レンダリング先の DOM ノードを格納する popupContainer 状態変数を宣言します。

const [popupContainer, setPopupContainer] = useState(null);

サードパーティウィジェットを作成するときは、ウィジェットによって返された DOM ノードを格納して、そこにレンダリングできるようにします。

useEffect(() => {
if (mapRef.current === null) {
const map = createMapWidget(containerRef.current);
mapRef.current = map;
const popupDiv = addPopupToMapWidget(map);
setPopupContainer(popupDiv);
}
}, []);

これにより、createPortal を使用して、popupContainer が使用可能になったら React コンテンツをレンダリングできます。

return (
<div style={{ width: 250, height: 250 }} ref={containerRef}>
{popupContainer !== null && createPortal(
<p>Hello from React!</p>,
popupContainer
)}
</div>
);

試せる完全な例を次に示します。

import { useRef, useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import { createMapWidget, addPopupToMapWidget } from './map-widget.js';

export default function Map() {
  const containerRef = useRef(null);
  const mapRef = useRef(null);
  const [popupContainer, setPopupContainer] = useState(null);

  useEffect(() => {
    if (mapRef.current === null) {
      const map = createMapWidget(containerRef.current);
      mapRef.current = map;
      const popupDiv = addPopupToMapWidget(map);
      setPopupContainer(popupDiv);
    }
  }, []);

  return (
    <div style={{ width: 250, height: 250 }} ref={containerRef}>
      {popupContainer !== null && createPortal(
        <p>Hello from React!</p>,
        popupContainer
      )}
    </div>
  );
}