【ITニュース解説】React: Building an Independent Modal with createRoot
2025年10月01日に「Dev.to」が公開したITニュース「React: Building an Independent Modal with createRoot」について初心者にもわかりやすく解説しています。
ITニュース概要
Reactでモーダルを表示する際、従来の条件分岐やContext管理に加え、`createRoot`を使う新手法が紹介された。これは、モーダルを独立したルートでレンダリングすることで、呼び出し元から関数を呼ぶだけでモーダルを表示・管理可能にし、コードをよりシンプルにする方法だ。
ITニュース解説
Reactは、ウェブサイトやアプリケーションの見た目(ユーザーインターフェース)を作るためのJavaScriptライブラリである。Reactを使うと、ウェブページを「コンポーネント」という小さな部品に分けて開発を進めることができ、それぞれの部品が独立して機能するため、開発がしやすくなる。今回解説する記事では、このReactで「モーダルウィンドウ」をどのように実装・管理するかについて、筆者の経験に基づく進化の過程と、最終的にたどり着いた新しいアプローチが紹介されている。
モーダルウィンドウとは、ウェブサイト上で何らかの操作を行った際に、現在の画面の上に覆いかぶさるように表示される小さなウィンドウのことだ。例えば、「本当に削除しますか?」といった確認メッセージや、ログインフォームなどがこれに該当する。ユーザーの注意を促したり、特定の情報を入力させたりするのに使われる。
筆者がReactを使い始めた当初、モーダルウィンドウは「条件付きレンダリング」という方法で表示されていた。これは、特定の条件(例えば、モーダルを表示すべきかどうかのフラグ)が真の場合にのみ、モーダルコンポーネントを画面に表示するというやり方だ。
1// 例:visibleがtrueの場合のみ<Modal />が表示される 2{visible && <Modal onOk={handleOk} />}
この方法では、モーダルを表示する親コンポーネント側で、表示・非表示のロジックを管理する必要がある。これは、コンポーネントが必要な時にのみ描画されるというReactの基本的な考え方に基づいている。
次に試されたのは、モーダルコンポーネント自身に表示・非表示の判断をさせる方法だ。これは、親コンポーネントからvisibleのようなプロパティ(props)をモーダルに渡し、モーダル内部でそのプロパティに基づいて表示するかどうかを決定するやり方である。
1// 例:visibleプロパティをモーダルに渡す 2<Modal visible={visible} onOk={handleOk} />
この方法では、親コンポーネントで条件付きレンダリングを行う必要はなく、シンプルにモーダルコンポーネントを配置するだけになる。しかし、どちらの方法も、モーダルを表示したい場所の近くにモーダルコンポーネントを記述するか、その表示状態を管理する変数を用意する必要があった。
さらに学習を進める中で、筆者は「コンテキストAPI」を利用したモーダル管理に移行した。コンテキストAPIは、Reactのコンポーネントツリー内でデータを共有するための機能で、これにより、深い階層にあるコンポーネントでもデータを簡単に受け取れるようになる。モーダルの管理では、モーダルを表示するための関数などをコンテキストとして提供し、useModal()のようなカスタムフックを使って、どのコンポーネントからでもモーダルを表示できるようにした。
1// 例:useModalフックを使ってモーダルを表示 2const {showModal} = useModal(); 3// ... 4showModal({ message: 'hello', onConfirm: () => { alert('clicked'); } });
この方法では、アプリケーションのどこか一箇所でModalProviderというコンポーネントを配置し、その中で実際のモーダルコンポーネントを描画する。これによって、個々のコンポーネントがモーダルの表示ロジックを持つ必要がなくなり、より柔軟にモーダルを呼び出せるようになった。しかし、やはりアプリケーションのルート要素や、それに近い場所にModalProviderを配置する必要があり、完全に独立しているとは言えなかった。
筆者は、最近のプロジェクトで再びモーダルが必要になった際、これまでの経験から「コードを分散させたくない」「単に関数を呼び出すだけでモーダルを表示したい」という強い思いを抱いた。そこで着想したのが、「新しいルート要素にコンポーネントを描画する」というアイデアである。この方法は、React 18から導入されたcreateRootという機能を利用する。
createRootは、ウェブページの任意のHTML要素をReactアプリケーションの「ルート(根)」として扱うことを可能にする関数だ。従来のReactでは、通常、アプリケーション全体で一つのルート(例えば、index.htmlに書かれた<div id="root"></div>)にすべてのコンポーネントが描画されていたが、createRootを使うと、一時的な要素を含め、複数のルートを動的に作成し、そこにReactコンポーネントを描画できるようになる。
このアプローチでは、モーダルを表示する際に以下の手順を踏む。まず、JavaScriptのdocument.createElement('div')を使って、ウェブページ上に一時的なdiv要素を新しく作成する。この要素はまだどこにも表示されていない、ただの空っぽの箱だ。次に、その一時的なdiv要素をdocument.body.append(tempElmt)というJavaScriptの機能を使って、ウェブページの最も外側の要素であるbodyタグの末尾に追加する。これで、一時的なdiv要素がウェブページの一部となるが、まだ何も表示されていない。
そして、この追加された一時的なdiv要素を、createRoot(tempElmt)としてReactの新しいルートとして設定する。これにより、この一時的なdiv要素を基準として、Reactコンポーネントを描画できるようになる。最後に、root.render(<ConfirmModal {...params} />)のようにして、実際に表示したいモーダルコンポーネントをこの新しいルートに描画する。これで、モーダルが画面に表示される。
モーダルが閉じられたり、キャンセルされたりした際には、tempElmt.remove()というJavaScriptの機能を使って、作成した一時的なdiv要素自体をウェブページから完全に削除する。これにより、モーダルが消えるだけでなく、関連するHTML要素もきれいに片付けられる。この一連の処理は、useConfirmModalというカスタムフックの中にまとめられており、その中のconfirm関数を呼び出すだけでモーダルの表示と片付けが完結する。
1// useConfirmModalフックの主要部分 2export const useConfirmModal = () => { 3 const confirm = useCallback( 4 ({ onConfirm, onCancel, ...params }: ConfirmParameters) => { 5 const tempElmt = document.createElement('div'); // 一時的なdiv要素を作成 6 document.body.append(tempElmt); // bodyに追加 7 const root = createRoot(tempElmt); // 新しいReactルートを作成 8 9 const handleConfirm = () => { 10 tempElmt.remove(); // モーダルを閉じたら要素を削除 11 onConfirm?.(); 12 }; 13 // ... 14 root.render( // モーダルコンポーネントを描画 15 <ConfirmModal 16 {...params} 17 onConfirm={handleConfirm} 18 onCancel={handleCancel} 19 /> 20 ); 21 }, 22 [] 23 ); 24 return { confirm }; 25};
このアプローチの最大の利点は、モーダルを表示するために、アプリケーションのメインのコンポーネントツリーに特別なコードを書く必要がなくなることだ。必要なのは、useConfirmModalフックを呼び出し、その中のconfirm関数を適切な場所で実行するだけである。
1// 例:ボタンクリックでモーダルを表示 2const { confirm } = useConfirmModal(); 3const handleDeleteClick = () => { 4 confirm({ 5 title: '本当に削除しますか?', 6 message: 'この操作は元に戻せません。', 7 titleColor: 'red', 8 confirmationPhrase: 'delete my account', 9 confirmText: 'はい、削除します', 10 }); 11};
これにより、モーダルコンポーネントは完全に独立した形で機能し、どの場所からでもシンプルに呼び出せるようになる。これは、コードの整理整頓や、再利用性の向上に大きく貢献する。
ただし、この方法はすべてのプロジェクトに最適というわけではない。プロジェクトの規模や要件によっては、他のモーダル管理方法の方が適している場合もあるだろう。しかし、createRootというReactの新機能を活用することで、これまでとは異なる視点からモーダルコンポーネントを管理できることを示している。この新しいアプローチは、Reactを使った開発における可能性を広げる一つの例として、非常に参考になるものだ。