7歳娘「パパ、ReactのHydration Errorってなんで起こるの？」

クライアントコンポーネントのお話です。
（サーバーコンポーネントは出て来ません）

# ある日の我が家

娘「ねぇ、パパ？」

ワイ「なんや、娘ちゃん？」

娘「ハイドって何？」

ワイ「おお、今時の子供はハイドさんのことを知らんのか」
ワイ「L'Arc〜en〜Cielのボーカリストさんやがな」

娘「違う、そっちじゃなくて」
娘「Reactの**ハイドレーション**のほう」

**ワイ「ファッ！？」**
ワイ「ハイドレーションのことをハイドって呼んでんのかい」

娘「うん」
娘「Next.jsでWebアプリを作っていたら、こんなハイドレーションエラーっていうのが起こっちゃって」

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295452/1e8695c7-3acc-fa6f-3722-ecd4d908f64e.png" width="572" alt="画面スクショ">


> 【和訳】
> エラー： テキストの内容がサーバーでレンダリングされた HTML と一致しません。
> 警告： テキストコンテンツが一致しませんでした。サーバー: "6" クライアント "5"

ワイ「おお〜」

娘「それで、ハイドレーションって何なのかな？って思ったの」

ワイ「なるほどなぁ〜」
ワイ「どんなWebアプリを作ってたん？」

娘「サイコロアプリだよ」
娘「こんな感じ」


### 画面

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295452/e8663c70-a1ac-8269-7e26-2d5e45306fc5.png" width="348" alt="画面スクショ">


娘「しかもね」
娘「6の目が出た場合には」
娘「↓こう表示されるんだよ〜」

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295452/0c29e9e5-3df0-30dc-4bc6-22f2304ef37d.png" width="348" alt="画面スクショ">


ワイ「おー、ええやないか」

娘「えへへ」
娘「でも、作ってる途中で、さっきのハイドレーションエラーが出ちゃったの・・・」

ワイ「サイコロっていう**ランダムなもの**を扱ってるわけやもんなぁ」
ワイ「これは確かに、気をつけんとハイドレーションエラーが起きそうやな」

娘「で、そのハイドレーションて何なの？」

# ハイドレーションとは

ワイ「ほな、ハイドレーションについて説明しようと思うんやけど」
ワイ「その前に、現状のコードがどんな感じなのか見せてもらえる？」

娘「うん」
娘「こんな感じだよ」

```tsx:TSX（一部抜粋）
  // saikoro() という関数で、1〜6のランダムな数値を生成。
  // それを useState の初期値として設定。
  const [saikoroNumber, setSaikoroNumber] = useState<number>(saikoro())

  return(
    <>
      <h1>サイコロの目</h1>
      <p>{ saikoroNumber }</p>
      {/* 6の場合はメッセージを表示 */}
      {saikoroNumber === 6 && <p>やった！6だね！</p>}
      <button onClick={roll}>サイコロをふる</button>
    </>
  )
```

ワイ「なるほどな」
ワイ「なあ、娘ちゃん」
ワイ「この`h1`要素とか`p`要素とか`button`要素が」
ワイ「どの段階でHTMLに書かれるかは知ってるか？」

娘「えーとね」
娘「まずは、ユーザーさんがブラウザを使って、このWebサイトにアクセスするでしょ？」
娘「それで、サーバからHTMLが返ってきて」
娘「その段階では───」

```html
<div id="__next"></div>
```

娘「↑こんな感じのHTMLが存在して」
娘「そこに、Reactが`h1`要素とか`p`要素とか`button`要素をレンダリングしてくれる感じでしょ？」
娘「そして───」

```html
  <div id="__next">
    <h1>サイコロの目</h1>
    <p>5</p>
    <button>サイコロをふる</button>
  </div>
```

娘「↑こんなDOMができあがる」
娘「そういう順番じゃないの？」

ワイ「いや、違うんや」
ワイ「サーバからHTMLが返ってきた時点で───」

```html
  <div id="__next">
    <h1>サイコロの目</h1>
    <p>5</p>
    <button>サイコロをふる</button>
  </div>
```

ワイ「↑こう、もう書かれてるんや」

娘「え、それはサーバサイドレンダリング（SSR）した場合じゃないの？」

ワイ「いや、SSRしてなくてもそうやで」
ワイ「Next.jsアプリをビルドすると、各ルート分のhtmlファイルが生成されるやん？」

娘「あ〜」
娘「`index.html`が1つだけ生成されるんじゃなくて」
娘「各ルート分、つまり子ページの分のファイルも生成されるんだね」

ワイ「せやで」
ワイ「その時点で、各ページのHTMLの中身は書かれてるんや[^1]」

娘「へぇ〜」
娘「そうなんだ」
娘「シングル・ページ・アプリケーションは、JavaScriptでHTMLをレンダリングするから」
娘「サーバから来た時点のHTMLは殆ど空っぽなのかと思ってた」

ワイ「ワイも昔はそう思ってたわ」
ワイ「でも、それだと───」

1. サーバから、ほぼ空っぽのHTMLが返ってくる
1. JavaScirpt（React含む）が読み込まれる
1. DOMが描画される

ワイ「こんな感じで、画面が表示されるまでに時間がかかってしまって」
ワイ「ユーザーさんをお待たせしてしまうやろ？」

娘「そっか」
娘「サーバからHTMLをもらった時点で」
娘「HTML要素たちが書かれていれば」
娘「その方が速いもんね」

ワイ「せやで」

# で、ハイドレーションとは

娘「で、ハイドレーションはいつ出てくるの？」

ワイ「せやったな」
ワイ「さっきの、サーバから返ってきたHTMLには」
ワイ「色んなHTML要素が書かれてはいるんやけど」
ワイ「まだJavaScriptのイベントリスナ等は登録されてないんや」
ワイ「せやから、クライアント側のJavaScriptによって」
ワイ「イベントリスナが登録されたり、インタラクティブな動作が追加されるんや」

娘「うんうん」

ワイ「それが**ハイドレーション**や」

娘「へぇ〜」
娘「サーバから来たHTMLに」
娘「Reactのインタラクティブな機能が注ぎ込まれる」
娘「それがハイドレーションなんだね」

ワイ「せやな」
ワイ「**水和**とか**水分補給**とか、そういう意味の単語らしいで」

娘「**水を加える**ってこと？」

ワイ「そんな感じやな」
ワイ「サーバから受け取った初期HTMLは、インタラクティブな機能を持たない**乾いたHTML**って感じで」
ワイ「そこに、クライアント側で水分を加えてやるイメージやな」

娘「へぇ〜」
娘「でも、何でそんなことするの？」

ワイ「サーバからHTMLが来た時点で、もう要素たちは書かれてるわけやから」
ワイ「それをわざわざクライアント側のJavaScirptで丸ごと置き換えたりせずに」
ワイ「サーバから受け取ったHTMLを再利用して、そこにイベントリスナなどを登録してやる感じや」

娘「そっか」
娘「そう考えると効率的だね」
娘「クライアントサイドで、インタラクティブなDOMをもう一度作り直すんじゃなくて」
娘「サーバから来たHTMLを再利用するんだね」

ワイ「せやで」

# じゃあ、ハイドレーションエラーとは

娘「ハイドレーションは分かったけど」
娘「私の作ったサイコロアプリは、何でハイドレーションエラーが出ちゃったんだろ？」

ワイ「うーん」
ワイ「乱数があるから、クライアントサイドのJSくんが困ってしまうんやろなぁ」


### クライアントサイドJSくんのお気持ち

JSくん「よーし、お仕事するでぇ」
JSくん「ハイドレーションしていくでぇ」
JSくん「ほな、サイコロページのコンポーネントを見てみよか」
JSくん「なるほど〜、こういったコンポーネントがあるってことは」
JSくん「たぶん、サーバからは───」

```html:クライアントサイドJSくんの予想するHTML
  <div id="__next">
    <h1>サイコロの目</h1>
    <p>5</p>
    <button>サイコロをふる</button>
  </div>
```

JS「↑これと一致するHTMLが返って来てるはずや！」
JS「ワイはその中にあるボタンに、イベントリスナなんかを加えていくでぇ」
JS「どれどれ、実際にサーバから返って来たHTMLを見てみよか[^2]」
JS「・・・ありゃ！？」

```html:実際にサーバくんが返した初期HTML
  <div id="__next">
    <h1>サイコロの目</h1>
    <p>6</p>
    <p>やった！6だね！</p>
    <button>サイコロをふる</button>
  </div>
```

JS「思ってたHTMLと違うやんけ・・・！」
JS「ほな、どのボタンにイベントリスナを加えればええんや・・・？」
JS「これ、再利用できひんわ！エラー投げて開発者さんに教えてあげんと！」

---

ワイ「↑こんなイメージやな」
ワイ「ランダムな数値を扱ったせいで」
ワイ「サーバから来る初期HTMLと、クライアントサイドJSくんが予想してたHTMLにズレが生じてしまって」
ワイ「ハイドレーション中にエラーが出てもうたんやな」

娘「そっかぁ」
娘「思ってたのと違うHTMLが来たら、使えないもんね」

ワイ「せやでぇ」
ワイ「サーバから貰った初期HTMLを再利用できへんとなると」
ワイ「パフォーマンスも低下してしまうから、よろしくないでぇ」


# じゃあ、どうすればいい？

娘「じゃあ、ランダムな値を使いたい場合はどうすればいいの？」

ワイ「クライアント側の予想するHTMLと、サーバから来る初期HTMLを、一致させなアカンから・・・」
ワイ「サイコロの値は、初期値で`null`を設定するようにして」
ワイ「`<p>5</p>`とか`<p>6</p>`とか、そういう乱数は表示されないようにしてみよか」

```tsx
  // ランダム値ではなく null を初期値とする。
  const [saikoroNumber, setSaikoroNumber] = useState<number | null>(null)

  return(
    <>
      <h1>サイコロの目</h1>
      {/* saikoroNumber が null の場合は表示しない */}
      {saikoroNumber !== null && <p>{ saikoroNumber }</p>}
      {/* 6の場合はメッセージを表示 */}
      {saikoroNumber === 6 && <p>やった！6だね！</p>}
      <button onClick={roll}>サイコロをふる</button>
    </>
  )
```

ワイ「こうしておけば、初期HTMLが作られるときには、サイコロの目は`null`になるから」
ワイ「サーバから返ってくるHTMLは───」

```html:サーバくんが返す初期HTML
<div id="__next">
  <h1>サイコロの目</h1>
  <button>サイコロをふる</button>
</div>
```

ワイ「↑こうなるはずや」

娘「なるほどね」
娘「サーバから返ってくる初期HTMLに、ランダム値が含まれないようにするんだね」

ワイ「そういうことや」
ワイ「そうすると、クライアントサイドJSくんも───」

> JSくん「サイコロの初期値は`null`やから」
> JSくん「↓こんなHTMLが返って来てるはずや！」

```html:クライアントサイドJSくんの予想するHTML
<div id="__next">
  <h1>サイコロの目</h1>
  <button>サイコロをふる</button>
</div>
```

> JSくん「ほな、サーバから来たHTMLを見てみるでぇ」

```html:実際にサーバくんが返した初期HTML
<div id="__next">
  <h1>サイコロの目</h1>
  <button>サイコロをふる</button>
</div>
```

> JSくん「よし、思ってた通りのHTMLが来てるで！」
> JSくん「ほな、ここにJSのインタラクティブな機能を注いでいくでぇ！」

ワイ「↑こう、ちゃんとハイドレーションができるわけやな」

娘「へぇ〜」
娘「でも、その直後くらいに、`null`からランダムな数値に変えないといけないよね？」
娘「そうしないと、サイコロの目の数が表示されないよ？」

ワイ「せやな」

娘「うまいことそのタイミングで乱数を生成するには、どうすればいいの？」

ワイ「そんなときは、アレを使うんや」

娘「アレ・・・？」

ワイ「ビルド時やサーバ側では実行されずに」
ワイ「クライアントサイドに来てから実行される───」
ワイ「そんな hooks があったよな」

娘「え・・・`useEffect()`を使うってこと？」

ワイ「せや」

```diff_tsx
const SamplePage = () => {
  const [saikoroNumber, setSaikoroNumber] = useState<number | null>(null)

+ useEffect(() => {
+   setSaikoroNumber(saikoro())
+ },[])

/* 以下略 */
```

ワイ「↑こうやな」
ワイ「`useEffect()`に渡した処理は、クライアント側でしか実行されへんし」
ワイ「ハイドレーションの後に実行されるんや」
ワイ「せやから、これでハイドレーションエラーは起こらんくなるで」

娘「なるほど〜」
娘「ありがとう、パパ！」

# まとめ

- コンポーネントは、クライアント側だけでなくビルド時やサーバ側でも実行される
- ハイドレーションとは
    - サーバから受け取った「乾いたHTML」に、クライアントサイドのインタラクティブな機能を注ぎ込むこと
        - イベントリスナを登録したり、ステート管理やその他の動的な機能を追加する
- ハイドレーションエラーとは
    - 「サーバから受け取った初期HTML」と「クライアントサイドJSが予期するHTML」が一致しない場合に起こるエラー
    - 予想と違うHTMLが来ると、どの要素に機能を注ぎ込めばいいか特定できず、再利用できない
- ハイドレーションエラーを起こさないためには
    - 「サーバから受け取った初期HTML」と「クライアントサイドJSが予期するHTML」が一致するようにコードを書く
    - 【例】サーバから来る初期HTMLにランダム値が含まれないようにする
- クライアント側に来てから実行したいものは`useEffect()`の中に書く
    - `useEffect()`はハイドレーションの後に実行される

娘「↑こういうことだね！」

ワイ「せやな！」

娘「`window`や`localStorage`なんかも、サーバ側やビルド時には存在しないから」
娘「サーバサイドとクライアントサイドの差異を生みそうだね！」

ワイ「せやな」
ワイ「そういう**ブラウザ固有のAPI**を使用する処理なんかも」
ワイ「`useEffect`内に移した方がええな」

娘「なるほどね〜」
娘「じゃあ、続きを実装してみる！」

ワイ「おう、頑張ってや！」

**〜おしまい〜**

[^1]: SWRやReact Queryで取得する外部APIのデータなど、初期では書かれていない部分も勿論あります

[^2]: 実際には「HTMLを見比べる」訳ではなく「サーバから来たHTMLをもとに作られた実DOM」と「クライアント側でReactが生成した仮想DOM」の状態が一致することを確認します


# 参考文献

- [hydrateRoot - React](https://react.dev/reference/react-dom/client/hydrateRoot#hydrating-server-rendered-html)