useState使いは造語です!全然そんなにuseState使いこなせてる勢ではないです・・!
勉強会で、Don’t mirror props in state / from React Docs BETA を読んでいて、これを記事化してみようと言われて、思いついたので書いてみました。ちょっとクスッとできれば嬉しいです
〜日常〜 Form実装を行いテストをする
Aさんはある日、以下のような簡単なFormモックを実装しました。
機能としては、以下を想定しています。
- ラジオボタンでサインアップを行うか、ログインを行うかを選ぶことができる
- チェックボックスをクリックすると、フォームが表示される
- ラジオボタンの選択状況によって、フォームの出しわけがある
- サインアップなら、Emailフォームと「サインアップ」ボタンを表示
- ログインなら、EmailとPasswordフォームと「ログイン」ボタンを表示
Aさんはフォームを実装した後、動作テストを行い、機能が満たされていることを確認しました
- サインアップをクリック → チェックボックスをクリックし、表示項目やフォーム動作を確認 → OK
- ログインをクリック → チェックボックスをクリックし、表示項目やフォーム動作を確認 → OK
Aさん「楽勝だね 」
〜忍び寄る恐怖〜
ところで、Aさんが作成したフォームの構成は、親と子のコンポーネントをもっています。
つまり、propsの受け渡しが発生しています。
親コンポーネントでサインアップかログインであるかの変数であるstateを子コンポーネントに渡しています。
export const OpenForm = () => {
const [val, setVal] = useState("サインアップ");
~
return(
~
{watchShowAge && <Form val={val} />} //変数valを渡している
)
}
子コンポーネントでは、渡されたpropsがuseStateの初期値にセットされています。
export const Form = (mirrorProps) => {
//変数のpropsをuseState初期値にセット
const [mirror, setMirror] = useState(mirrorProps);
~
//state管理でformの出しわけをする
if (mirror.val === "ログイン") {
return (
<>
<form onSubmit={handleSubmit(onSubmit)}>
~
{/* 出しわけ内容は省略 */}
Aさん「ところで、useStateの初期値に変数をセットした時の挙動はどうなるんだっけ? 」
〜悲劇〜
そんなある日、探索的テスト結果として、不具合があがってきました。
チェックボックスにチェックを入れてからラジオボタンを変更すると、機能のうち
3.ラジオボタンの選択状況によって、フォームの出しわけがある
が満たせなくなっています。
これの理由はReact Docs BETA に記載してあり、useStateの初期値に変数をセットした時、stateは、最初のレンダリング時にのみ初期化されるとあります
The problem is that if the parent component passes a different value of messageColor later (for example, 'red' instead of 'blue'), the color state variable would not be updated! The state is only initialized during the first render.
This is why “mirroring” some prop in a state variable can lead to confusion.
上記訳→"問題は、後から親コンポーネントが別の値をmessageColor(引数)を子コンポーネントに渡しても(例えば、blueの代わりにredを渡しても)、color変数(=useState初期値)は更新されないということです!stateは、最初のレンダリング時にのみ初期化されます。
これが、state変数にpropsを「ミラーリング」すると混乱を招く可能性がある理由です。"
Aさん「修正します!(やっちまった)」
〜解決〜
解決方法もReact Docs BETA に記載してあり、
f you want to give it a shorter name, use a constant:
上記訳→"代わりに、コード内でmessageColorプロパティを直接使用してください。より短い名前を付けたい場合は、定数を使用します"
つまり、以下のように修正するだけです
export const Form = (mirrorProps) => {
const mirror = mirrorProps;
Aさん「変数をpropsとして渡した先では、普通に定義しよう」
〜mirror propsはあなたの傍にいるかもしれない〜
とはいえ、、useStateの初期値に変数を設定するシーンはでてくるかもしれません。
その時、思い出してあげてください。
プロップスのミラーリングでは小コンポーネントをはじめにレンダリングした時にしか渡せないことを・・・・・
挙動としてはコンポーネントが再マウントされれば直るので、この挙動を認識できていて、「そういう仕様です」と言えるものであれば、プロップスのミラーリングが採用されることもあるとは思います。ただ、個人的にはコードをみてパッとそのことが分かりにくいから違和感が大きいです。
別の恐怖もみたいあなたに
同じような現象を引き起こす例をご用意しました
勉強会仲間のアドバイスでChatGPTに聞いてみて、リトライを繰り返してGPT君が作ってくれたものです
ぜひEditしてみてください・・・!
参考文献
- propsのミラーリングの解説