どうも、ちゃんなるです!
今回は過去の2つの記事の内容を合わせてコーディングします🔥(ソースコード on Github)。
概要
今回やりたいこと
Reactコンポーネント内へレンダリング対象となる複数のDOM要素をPropsとして渡し、それ用いてレンダリングを行う。
おことわり
今回実装する内容は画面の表示はできますがWarningが出ます。
正攻法ではないrender()
の使い方をしているためです。
ご了承くださいませ🙇♂️
実現方法
まず、レンダリングしたい要素を取得します。そしてそれをAppコンポーネントに渡します。
このスコープ内では画面上に影響を与えないよう仮のDOM要素(ここではtentativeElement
)を作成し、それに対してAPPコンポーネントをレンダリングします。
// index.js import ReactDOM from 'react-dom/client' import { App } from './App' /*レンダリングしたい要素*/ const domContents = document.querySelectorAll('#selected') /*Reactコンポーネントを実行させるための仮のDOM要素*/ const tentativeElement = document.createElement('div') const tentativeRoot = ReactDOM.createRoot(tentativeElement) tentativeRoot.render( /*Reactコンポーネントにレンダリングしたい要素を渡す (レンダリングはAppコンポーネント内部にて)*/ <App domContents={domContents} /> )
ちなみにHTMLの内容は下記のようなものです。
<div> <div id="selected" > </div> <div> ... </div> <div id="selected" > </div> </div>
Appコンポーネントの内容です。
// App.tsx import ReactDOM from 'react-dom/client' type DomContents = { domContents: NodeListOf<Element> } export const App = (props: DomContents) => { /*ここはReactコンポーネント内部なので、任意のReact Hooksが使える Hooksによって作成した情報などをレンダリング時に渡してあげられる*/ /*Propsとしてレンダリングの起点となるDOM要素を受け取り、*/ props.domContents.forEach(element => { /*このコンポーネント内でcreateRootに渡してあげる*/ const root = ReactDOM.createRoot(element) root.render( <Content /> ) }) return null } const Content = () => { return ( <h1>Hello, World!!!</h1> ) }
ちなみに、下記ふたつのWarningが出ます。
Warning: Render methods should be a pure function of props and state; triggering nested component updates from render is not allowed. If necessary, trigger nested updates in componentDidUpdate.
:render()
にPropsやStateのような状態を持つ変数(ここではprops
)を渡すのは危険です。レンダリング時に状態を変更するような処理があるとレンダリング処理が無限ループするかもしれません。
今回はprops
として渡しているDOM要素に変更を加える処理がないので、無限ループの心配はありません。DOM要素をPropsで渡さずApp.tsx
内で取得することで解消できるかもしれません。
Warning: You are calling ReactDOMClient.createRoot() on a container that has already been passed to createRoot() before. Instead, call root.render() on the existing root instead if you want to update it.
:createRoot()
に渡された要素の内部で再度createRoot()
を呼んでます。もしroot
(ここではindex.js
内でcreateRoot()
して作られたtentativeRoot
を指す)を更新したいならば、代わりにroot.render()
を呼び出しましょう。
今回はcreateRoot()
に渡している要素はそれぞれ異なります。なのでこのWarningを解消するすべはなさそうです。
まとめと課題
Warningが出てはしまうものの、JSファイル(index.js
)側ではなくReactコンポーネント(App.tsx
)側で複数のDOM要素を起点としたレンダリング処理を実行させられました👍
上述した2つのエラーのうち1つ目は「Propsの受け渡しをしないようにする」、すなわち「Appコンポーネント内でDOM要素を取得するようにする」ことで解決するのでは?と思い次のように修正してみましたが…結果変わらず💦
// App.tsx import ReactDOM from 'react-dom/client' const domContents = document.querySelectorAll('#selected') export const App = () => { domContents.forEach(element => { const root = ReactDOM.createRoot(element) root.render( <Content /> ) }) return null } const Content = () => { return ( <h1>Hello, World!!!</h1> ) }
Warningを回避する方法、そもそももっと良い複数DOMへのレンダリング方法がわかったら投稿します🙋♂️
環境
実装や動作確認の際に使ったツールたちです。
OS | macOS Monterey Version 12.5 |
Node.js | v18.6.0 |
create-react-app | v5.0.1 |
*対象のアプリはcreate-react-app
を用いて作成しました。