やろうとしたこと
配列をシャッフルしてから表示しようとしました。
ざっくりと下のような感じです。
import { useState } from 'react'
export const Sample = () => {
const [numberArray, setNumberArray] = useState<number[]>([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
const shuffleArray = () => { 配列をシャッフルする処理 };
shuffleArray(numberArray);
setNumberArray([...numberArray])
return (
<div>
{numberArray.map((number, index) => {
<p key={index}>{number}</p>
})}
</div>
)
}
エラー
Warning: Prop xxx did not match. Server: aaa Client: bbb
という内容のエラーが表示されました。
原因
ReactではSSRした内容とCSRした内容を比較し、もし同じ場合はSSRした値をそのまま利用する(これをhydorateという)。
上記では配列をシャッフルしているため、SSRした内容とCSRした内容が異なっていたことが原因。
ReactDOM.hydrate()はReactDOMServer、つまりサーバサイドで作成されたHTMLをクライアントサイドで再利用するメソッドです。
ReactDOM.hydrate()はサーバサイドとクライアントサイドのレンダリング結果が一致することを期待します。
結果が一致する場合はクライアントサイドでのレンダリングをスキップ、不一致の場合はクライアントサイドで再度レンダリングをします。
【Next.js】Hydration時にReact.hydrate()による警告が発生するケースとその解決方法
↓ こんな感じ
SSRで生成された配列: [1, 5, 7, 3, 4, 0, 2, 8, 9, 6]
CSRで生成された配列: [5, 9, 2, 3, 1, 8, 0, 4, 6, 9]
対処
useEffect内で処理を実行する。
import { useState, useEffect } from 'react'
export const Sample = () => {
const [numberArray, setNumberArray] = useState<number[]>([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
const shuffleArray = () => { 配列をシャッフルする処理 };
useEffect(() => {
shuffleArray(numberArray);
setNumberArray([...numberArray])
},[])
return (
<div>
{numberArray.map((number, index) => {
<p key={index}>{number}</p>
})}
</div>
)
}
流れとしては下のようになります
エラー時
初期状態: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
↓
SSR: [1, 5, 7, 3, 4, 0, 2, 8, 9, 6] // シャッフルされた
↓
CSR(hydration実行): [5, 9, 2, 3, 1, 8, 0, 4, 6, 9] // SSRの結果と異なる
↓
Warning(一応CSRの内容で画面描画はされます。)
useEffect使用時
初期状態: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
↓
SSR: [1, 5, 7, 3, 4, 0, 2, 8, 9, 6] // シャッフルされた
↓
CSR(hydration実行): [5, 9, 2, 3, 1, 8, 0, 4, 6, 9] // SSRの結果と異なる
↓
useEffectが呼び出され、再レンダリングされる。: [3, 6, 7, 1, 0, 9, 2, 5, 4, 8] // 別の配列が生成される
↓
画面描画:[3, 6, 7, 1, 0, 9, 2, 5, 4, 8]
ただし、公式によると、この方法はパフォーマンスの低下を招くので注意してくださいとのことです。
このアプローチでは 2 回レンダーが発生することによりコンポーネントのパフォーマンスが低下しますので、注意して使用してください。