Reactでアプリを生成している時に、とあるComponentが内部で参照しているObjectの状態を変更したけど、renderで表示されているものにはその変更が反映されなくて非常に困ったという内容です。
問題になったコード
仕様はこんな感じのもの
- functionsは内部で問い合わせ先URLとコマンド名を持っている
- FunctionCallerは画面にコマンド名のボタンを描画する
- FunctionCallerのボタン押下で問い合わせ先URLに対してコマンドを非同期で実行して、サーバからのレスポンスを描画するComponent
- 問い合わせ先のURLは入力フィールドがあってユーザが動的に変更が可能(このコードは載せてません。)
<Panel header={<h3>functions</h3>}> {functions.filter((data) => data.type == 'function').map((data, i, a) => { return <FunctionCaller server={data.server} name={data.functionName} key={`call-function-button-${i}`}/> })} </Panel>
何が起きたのか?
Componentの問い合わせ先URLを変更しても、ボタンを押すと昔の問い合わせ先に対してリクエストが飛んでしまっていた。
つまり、renderで新しい状態に変更されたコンポーネントが描画されていなかった。
なお、問い合わせ先URLはconsole.logで新しいものに置き換わっていることを確認している。
原因
画面上に表示するもの(cssやattributesや文字列など)は一切変更されていないため、DOMの差分がないので描画の更新が起きず、新しい状態に変更されたコンポーネントが描画されず、画面上には古い状態のコンポーネントが残っていた。
解決策
今回はforEachで表示していたので上記のようにkeyで渡す文字列に問い合わせ先のシグネチャも含めることで、問い合わせ先を変えたらkeyも変わるようにした。
keyが問い合わせ先に応じて変更されるため、問い合わせ先だけ変更しても新しく生成されたコンポーネントが画面に描画されるようになった。
まとめ
Reactは差分のみを描画するため、今回のように画面上(HTML上)に現れない変更を反映させたい時には注意が必要ということがわかった。componentには状態に応じて一意に定るkeyやidを付与する癖をつけておいた方がいいのかもしれない。(レアケースだとは思うが。。。)