関数コンポーネントの中で関数コンポーネントを定義してはいけません！！！

## はじめに
は〜いこんにちは〜
どうもおでこです。よろしくお願いします。

この記事は、実際に起きた実務中の悲劇が元になっています。
自分で書いたコードでバグを生んでしまい、先輩の力もお借りし、壮絶なデバッグを経てなんとか解決できたけど、めっちゃ時間かかってしまった…というものです。

**Reactむじぃ〜〜〜〜〜〜** と思っていたら、まさかのReact公式ドキュメントの落とし穴にも記載されている内容でした。初歩的なミスに引っかかってしまったというわけですね！！！ありがたいねぇ！！！

二度と同じミスをしないように、戒めとして記事に残したいと思います。

この記事で伝えたいことは1つです。これだけ覚えて帰ってください。

:::note alert
**関数コンポーネントの中で関数コンポーネントを定義してはいけません！！！**
:::

## 自分で産んだ悲劇のバグ
まずはバグの内容を紹介します。
1言で表すと **「意図せずにコンポーネントがアンマウントされてしまうバグ」** です。

実装したのは、３種類のタブによって表示するコンポーネントを切り替えるUIでした。
このコンポーネントとは別に、画面全体で管理しているstateを1つ定義し、描画しています。便宜上、Add App Countボタンとカウンターを表示しており、クリックするとカウントが1増えます。

https://codesandbox.io/embed/ysmklw?view=preview&module=%2Fsrc%2FApp.tsx

まず、タブを`TabA`から`TabC`に切り替えてみてください。
`Add App Count`は画面全体で管理しているstateなので、タブを切り替えたとしてもカウンターの数値は引き継がれます。

さて、問題はここからです。
```typescript:BComponent
 useEffect(() => {
    alert("B component mounted");
  }, []);
```
BComponentでは上記のようなuseEffectが定義されています。実際の業務ではこのuseEffectの中でfetchする処理を行っていますが、便宜上アラートを表示しています。

依存配列が空なので、BComponentが初めて画面に描画されたタイミング（タブを切り替えたとき）で、1度だけアラートが表示されるはずです。

では、タブを`TabC`から`TabB`に切り替えてみてください。

アラートが2回表示されますね🤔
何やら不穏が漂ってきました…

BComponentには、BComponent内で定義しているカウンターを表示しています。
`Add B Count`ボタンをクリックすると、カウントが1増えます。

ここで、`Add App Count`をクリックするとどうなるでしょうか？
理想は、`Add App Count`のカウンターの数値だけが1増えることです。しかし、実際にクリックしてみると、タブを切り替えていないのに再びアラートが2回表示されます。さらに、`Add B Count`のカウンターの数字がリセットされてしまいます。

## バグの原因
起きているバグから以下のような仮説が立ちました。

:::note info
タブBに切り替えたとき、`BComponent`で定義している`useEffect`が2回発火、かつカウンターの数値がリセットされてしまう
  => `BComponent`が何かしらの理由で1度アンマウントされているのではないか
:::

この仮説のもと、`BComponent`をレンダリングしている箇所を確認してみます。
```typescript:App.tsx
export default function App() {
  const [type, setType] = useState<TabType>("A");
  const [count, setCount] = useState(0);

  const Components: FC = () => {
    switch (type) {
      case "A":
        return <AComponent />;
      case "B":
        return <BComponent />;
      case "C":
        return <CComponent />;
    }
  };

  return (
    <div className="App">
      // ...省略
      
      <div className="container">
        <Components />
      </div>
      <h2>{count}</h2>
      <button onClick={() => setCount(count + 1)}>Add App Count</button>
    </div>
  );
}
```
このコードを見ると、Appコンポーネント内で`Components`という関数を定義し、それをレンダリングしていますね。実はこれが問題の原因です。

**「ってお前、関数コンポーネントの中で関数コンポーネントを定義しとるやないかい！！！！！！！」**

と今なら突っ込めるのですが、このコードを書いてデバッグ地獄にハマっているときは中々気づけませんでした。

なぜ、この書き方ではタブを切り替えたとき、一度コンポーネントがアンマウントされてしまうのでしょうか？

## 関数コンポーネントの中で関数コンポーネントを定義してはいけない理由
関数コンポーネントの中で関数コンポーネントを定義すると、**再レンダリングが起こるたびに関数コンポーネントが再定義されてしまう**ため、バグが発生することがあります。

今回の例で考えてみます。

上記のコードで、以下の操作を行うと問題が発生しました。

1. タブを切り替えると、`type`が変更されるため、Appコンポーネントが再レンダリングされます。
2. `Add App Count`ボタンをクリックして`count`が変更されるときも、Appコンポーネントが再レンダリングされます。

このとき、Appコンポーネント内で定義しているComponents関数も再定義されます。つまり、レンダリングのたびに新しいComponents関数が作られることになります。見た目には同じものが表示されているように見えますが、内部的には全く別の関数コンポーネントです。

レンダリングする`<Components />`はレンダリングのたびに別物になるため、これまで表示していた`BComponent`は一度アンマウントされ、再度レンダリングされます。そのため、以下のようなバグが発生します。

1. BComponentのuseEffectが再度実行される。
2. カウンターの状態がリセットされる。


## React公式ドキュメントの落とし穴にも記載されている
この内容は、React公式ドキュメントにも記載されています。

>同じ位置に異なるコンポーネントをレンダーしているので、React はそれより下のすべての state をリセットします。これはバグやパフォーマンスの問題につながります。この問題を避けるために、常にコンポーネント関数はトップレベルで宣言し、定義をネストしないようにしてください。

https://ja.react.dev/learn/preserving-and-resetting-state#different-components-at-the-same-position-reset-state

やりがちなミスということでしょうか…
まんまとハマってしまいました😭

## 終わりに
このバグにハマってしまったせいで、予定工数よりも大幅に遅れてしまいました(´・ω・｀)

今回はuseEffectでアラートを表示するだけでしたが、実際にはAPIコールする結構重めの非同期処理が関わっていました。タブを切り替えたり、カウンターが変わるたびに余分にコールしてしまうと、パフォーマンスが落ちてしまいます。

しっかりと気づいて修正できたので良かったですが、もう二度と同じミスをしないようにしたいです💡

最後に戒めとしてもう一度。

:::note alert
**関数コンポーネントの中で関数コンポーネントを定義してはいけません！！！**
:::

ありがとうございました。