hydrateRoot
hydrateRoot
を使用すると、react-dom/server
によって事前生成した HTML コンテンツが含まれるブラウザ DOM ノード内に、React コンポーネントを表示できます。
const root = hydrateRoot(domNode, reactNode, options?)
リファレンス
hydrateRoot(domNode, reactNode, options?)
hydrateRoot
を呼び出して、サーバ環境で事前に React がレンダーした HTML に対して React を「アタッチ」します。
import { hydrateRoot } from 'react-dom/client';
const domNode = document.getElementById('root');
const root = hydrateRoot(domNode, reactNode);
React は、domNode
内に存在する HTML にアタッチし、その内部の DOM の管理を引き継ぎます。React で完全に構築されたアプリには、ルートコンポーネントを引数にした hydrateRoot
呼び出しが通常 1 つのみ存在します。
引数
-
domNode
: サーバ上でルート要素としてレンダーされた DOM 要素。 -
reactNode
: 既存の初期 HTML をレンダーするために使用された “React ノード”。これは通常、ReactDOM Server
のメソッド(例:renderToPipeableStream(<App />)
)でレンダーされた JSX、例えば<App />
になります。 -
省略可能
options
: この React ルートのオプションを含むオブジェクト。- Canary only optional
onCaughtError
: Callback called when React catches an error in an Error Boundary. Called with theerror
caught by the Error Boundary, and anerrorInfo
object containing thecomponentStack
. - Canary only optional
onUncaughtError
: Callback called when an error is thrown and not caught by an Error Boundary. Called with theerror
that was thrown and anerrorInfo
object containing thecomponentStack
. - optional
onRecoverableError
: React が自動的にエラーから回復したときに呼び出されるコールバック。Called with theerror
React throws, and anerrorInfo
object containing thecomponentStack
. Some recoverable errors may include the original error cause aserror.cause
. - 省略可能
identifierPrefix
: React がuseId
によって生成する ID に使用する文字列プレフィックス。同じページ上に複数のルートを使用する際に、競合を避けるために用います。サーバ上で使用されたものと同じプレフィックスでなければなりません。
- Canary only optional
返り値
hydrateRoot
は、render
と unmount
の 2 つのメソッドを持つオブジェクトを返します。
注意点
hydrateRoot()
は、レンダーされたコンテンツがサーバでレンダーされたコンテンツと同一であることを期待しています。不一致はバグとして扱い修正する必要があります。- 開発モードでは、React はハイドレーション中の不一致について警告します。不一致が発生した場合、属性の違いが修正される保証はありません。これはパフォーマンス上の理由から重要です。なぜならほとんどのアプリでは、不一致はまれであり、すべてのマークアップを検証することは非常に高コストになるからです。
- アプリには通常、
hydrateRoot
呼び出しは 1 つだけ存在します。フレームワークを使用している場合、フレームワークがこの呼び出しを行うかもしれません。 - アプリがクライアントでレンダーする形式であり、事前レンダーされた HTML がない場合、
hydrateRoot()
は使用できません。代わりにcreateRoot()
を使用してください。
root.render(reactNode)
root.render
を呼び出して、ブラウザ DOM 要素内にハイドレーションされた React ルート内の React コンポーネントを更新します。
root.render(<App />);
React はハイドレーションされた root
内の <App />
を更新します。
引数
reactNode
: 更新したい “React ノード”。通常は<App />
のような JSX ですが、createElement()
で構築した React 要素や、文字列、数値、null
、またはundefined
を渡すこともできます。
返り値
root.render
は undefined
を返します。
注意点
- ルートがハイドレーションを完了する前に
root.render
を呼び出すと、React は既存のサーバレンダリングされた HTML コンテンツをクリアし、ルート全体をクライアントでのレンダーに切り替えます。
root.unmount()
root.unmount
を呼び出して、React ルート内にレンダーされたツリーを破棄します。
root.unmount();
React で完全に構築されたアプリには、通常、root.unmount
の呼び出しは一切ありません。
これは主に、React ルートの DOM ノード(またはその祖先のいずれか)が他のコードによって DOM から削除される可能性がある場合に有用です。例えば、非アクティブなタブを DOM から削除する jQuery のタブパネルがあると想像してみてください。タブが削除されると、(React ルートを含んだ)内部のすべてが DOM から削除されます。その場合、削除されたルートのコンテンツの管理を「停止」するよう React に伝えるために root.unmount
を呼び出す必要があります。そうしないと、削除されたルート内のコンポーネントは、データ購読などのグローバルリソースをクリーンアップして解放する必要があるということが分からないままになります。
root.unmount
を呼び出すことで、ルート内のすべてのコンポーネントがアンマウントされ、React がルート DOM ノードから「切り離され」ます。これには、ツリー内のイベントハンドラや state の削除も含まれます。
引数
root.unmount
は引数を受け付けません。
返り値
root.unmount
は undefined
を返します。
注意点
-
root.unmount
を呼び出すと、ツリー内のすべてのコンポーネントがアンマウントされ、React がルート DOM ノードから「切り離され」ます。 -
root.unmount
を呼び出した後、同一ルートで再度root.render
を呼び出すことはできません。アンマウント済みのルートでroot.render
を呼び出そうとすると、“Cannot update an unmounted root” というエラーがスローされます。
使用法
サーバでレンダーされた HTML のハイドレーション
あなたのアプリの HTML が react-dom/server
によって生成されている場合、クライアント上それに対するハイドレーションを行う必要があります。
import { hydrateRoot } from 'react-dom/client';
hydrateRoot(document.getElementById('root'), <App />);
これにより、ブラウザ DOM ノード内にあるサーバからの HTML がハイドレーションされ、あなたのアプリの React コンポーネントとなります。通常、これはスタートアップ時に一度のみ行います。フレームワークを使用している場合はあなたの代わりにこの呼び出しが自動で行われるかもしれません。
アプリのハイドレーション時に React は、サーバで生成された初期 HTML に、あなたのコンポーネントのロジックを「アタッチ」します。ハイドレーションにより、サーバからの初期 HTML スナップショットが、ブラウザで動作する完全にインタラクティブなアプリに変化します。
import './styles.css'; import { hydrateRoot } from 'react-dom/client'; import App from './App.js'; hydrateRoot( document.getElementById('root'), <App /> );
通常、hydrateRoot
を再度呼び出したり、複数の場所で呼び出したりする必要はありません。ここから先は、React がアプリケーションの DOM を管理しています。UI を更新するには、コンポーネントは state を使うことになるでしょう。
ドキュメント全体のハイドレーション
React で完全に構築されたアプリケーションは、<html>
タグを含むドキュメント全体を JSX としてレンダーすることができます。
function App() {
return (
<html>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/styles.css"></link>
<title>My app</title>
</head>
<body>
<Router />
</body>
</html>
);
}
ドキュメント全体をハイドレートするには、hydrateRoot
の最初の引数として document
グローバル変数を渡します。
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';
hydrateRoot(document, <App />);
やむを得ないハイドレーション不一致エラーの抑制
サーバとクライアントの間で、単一の要素の属性やテキストコンテンツがやむを得ない理由で異なる場合(たとえば、タイムスタンプなど)、ハイドレーションの不一致警告を抑制することができます。
要素のハイドレーション警告を抑制するには、suppressHydrationWarning={true}
を追加します。
export default function App() { return ( <h1 suppressHydrationWarning={true}> Current Date: {new Date().toLocaleDateString()} </h1> ); }
これは単一レベルの深さまでしか機能せず、避難ハッチとしての使用を意図しています。過度に使用しないでください。これを使用してもテキストコンテンツ以外の場合は React は違いを修正しようとはしないため、将来の更新まで一貫性が保たれない可能性があります。
クライアントとサーバで異なるコンテンツの処理
サーバとクライアントで意図的に異なるものをレンダーする必要がある場合、2 回に分けたレンダーを行うことができます。クライアントで異なるものをレンダーするコンポーネントは、isClient
のような state 変数を読み取るようにし、この変数をエフェクト内で true
に設定することができます。
import { useState, useEffect } from "react"; export default function App() { const [isClient, setIsClient] = useState(false); useEffect(() => { setIsClient(true); }, []); return ( <h1> {isClient ? 'Is Client' : 'Is Server'} </h1> ); }
この方法では、初回のレンダーはサーバと同じコンテンツをレンダーし、不一致を回避しますが、追加のレンダーがハイドレーションの直後に同期的に行われます。
ハイドレーションされたルートコンポーネントの更新
ルートのハイドレーションが完了した後、root.render
を呼び出してルートの React コンポーネントを更新することができます。createRoot
の場合とは異なり、初期コンテンツはすでに HTML としてレンダーされているため、通常はこれを行う必要はありません。
ハイドレーションの後のどこかのタイミングで root.render
を呼び出し、コンポーネントツリーの構造が以前にレンダーされたものと一致していれば、React は state を保持します。以下の例で入力フィールドにタイプできることに着目してください。つまり毎秒 render
が繰り返し呼び出されていますが、更新により DOM が破壊されていないということです。
import { hydrateRoot } from 'react-dom/client'; import './styles.css'; import App from './App.js'; const root = hydrateRoot( document.getElementById('root'), <App counter={0} /> ); let i = 0; setInterval(() => { root.render(<App counter={i} />); i++; }, 1000);
ハイドレーションされたルートで root.render
を呼び出すことは滅多にありません。通常、代わりにコンポーネントの中で state を更新 します。
Show a dialog for uncaught errors
By default, React will log all uncaught errors to the console. To implement your own error reporting, you can provide the optional onUncaughtError
root option:
import { hydrateRoot } from 'react-dom/client';
const root = hydrateRoot(
document.getElementById('root'),
<App />,
{
onUncaughtError: (error, errorInfo) => {
console.error(
'Uncaught error',
error,
errorInfo.componentStack
);
}
}
);
root.render(<App />);
The onUncaughtError option is a function called with two arguments:
- The error that was thrown.
- An errorInfo object that contains the componentStack of the error.
You can use the onUncaughtError
root option to display error dialogs:
import { hydrateRoot } from "react-dom/client"; import App from "./App.js"; import {reportUncaughtError} from "./reportError"; import "./styles.css"; import {renderToString} from 'react-dom/server'; const container = document.getElementById("root"); const root = hydrateRoot(container, <App />, { onUncaughtError: (error, errorInfo) => { if (error.message !== 'Known error') { reportUncaughtError({ error, componentStack: errorInfo.componentStack }); } } });
Displaying Error Boundary errors
By default, React will log all errors caught by an Error Boundary to console.error
. To override this behavior, you can provide the optional onCaughtError
root option for errors caught by an Error Boundary:
import { hydrateRoot } from 'react-dom/client';
const root = hydrateRoot(
document.getElementById('root'),
<App />,
{
onCaughtError: (error, errorInfo) => {
console.error(
'Caught error',
error,
errorInfo.componentStack
);
}
}
);
root.render(<App />);
The onCaughtError option is a function called with two arguments:
- The error that was caught by the boundary.
- An errorInfo object that contains the componentStack of the error.
You can use the onCaughtError
root option to display error dialogs or filter known errors from logging:
import { hydrateRoot } from "react-dom/client"; import App from "./App.js"; import {reportCaughtError} from "./reportError"; import "./styles.css"; const container = document.getElementById("root"); const root = hydrateRoot(container, <App />, { onCaughtError: (error, errorInfo) => { if (error.message !== 'Known error') { reportCaughtError({ error, componentStack: errorInfo.componentStack }); } } });
Show a dialog for recoverable hydration mismatch errors
When React encounters a hydration mismatch, it will automatically attempt to recover by rendering on the client. By default, React will log hydration mismatch errors to console.error
. To override this behavior, you can provide the optional onRecoverableError
root option:
import { hydrateRoot } from 'react-dom/client';
const root = hydrateRoot(
document.getElementById('root'),
<App />,
{
onRecoverableError: (error, errorInfo) => {
console.error(
'Caught error',
error,
error.cause,
errorInfo.componentStack
);
}
}
);
The onRecoverableError option is a function called with two arguments:
- The error React throws. Some errors may include the original cause as error.cause.
- An errorInfo object that contains the componentStack of the error.
You can use the onRecoverableError
root option to display error dialogs for hydration mismatches:
import { hydrateRoot } from "react-dom/client"; import App from "./App.js"; import {reportRecoverableError} from "./reportError"; import "./styles.css"; const container = document.getElementById("root"); const root = hydrateRoot(container, <App />, { onRecoverableError: (error, errorInfo) => { reportRecoverableError({ error, cause: error.cause, componentStack: errorInfo.componentStack }); } });
Troubleshooting
I’m getting an error: “You passed a second argument to root.render”
A common mistake is to pass the options for hydrateRoot
to root.render(...)
:
To fix, pass the root options to hydrateRoot(...)
, not root.render(...)
:
// 🚩 Wrong: root.render only takes one argument.
root.render(App, {onUncaughtError});
// ✅ Correct: pass options to createRoot.
const root = hydrateRoot(container, <App />, {onUncaughtError});