はじめに
uhyoさんの「今日のReactクイズ」で紹介されていた問題を実際に検証してみました。
チェックボックスを切り替えるとCounterのステートがリセットされる現象について、その原因と対処法をまとめます。
問題のコード
export default function App() {
const [checked, setChecked] = useState(false);
return (
<div className="App">
<p>
<input
type="checkbox"
checked={checked}
onClick={() => setChecked((c) => !c)}
/>
</p>
{!checked && <Counter />}
{checked && <Counter />}
</div>
);
}
function Counter() {
const [count, setCount] = useState(0);
return (
<p>
<button type="button" onClick={() => setCount((c) => c + 1)}>
{count}
</button>
</p>
);
}
クイズの答え:リセットされる
このコードでは、チェックボックスを切り替えるたびにカウンターがリセットされます。
なぜリセットされるのか
理由は、Reactのコンポーネント識別の仕組みにあります。
{!checked && <Counter />} // 1つ目の位置
{checked && <Counter />} // 2つ目の位置
この2つの<Counter />は、DOMツリー上の異なる位置に配置されています。
Reactは、checkedの値が変わると:
- 古い位置の
<Counter />をアンマウント(破棄) - 新しい位置に別の
<Counter />をマウント(新規作成)
という処理を行います。その結果、内部のstateもリセットされます。
対処法(本来は不要だが)
クイズの本質は「リセットされることを理解する」ことですが、もし保持したい場合の解決策を紹介します。
解決策1:keyプロパティを使用
{!checked && <Counter key="counter" />}
{checked && <Counter key="counter" />}
同じkeyを持つことで、Reactは同一コンポーネントとして認識します。
解決策2:条件分岐をなくす
export default function App() {
const [checked, setChecked] = useState(false);
return (
<div className="App">
<p>
<input
type="checkbox"
checked={checked}
onClick={() => setChecked((c) => !c)}
/>
</p>
<Counter />
</div>
);
}
そもそも条件分岐せず、常に同じ位置に配置すればリセットされません。
まとめ
- Reactは条件分岐で異なる位置に配置されたコンポーネントを別物として扱う
- そのため、条件が変わるとstateがリセットされる
- 理解することが重要で、無理に保持しようとする必要はない
- どうしても保持したい場合は
keyを使うか、条件分岐をなくす
JISOUのメンバー募集中!
プログラミングコーチングJISOUでは、新たなメンバーを募集しています。
日本一のアウトプットコミュニティでキャリアアップしませんか?
興味のある方は、ぜひホームページをのぞいてみてください!
▼▼▼
