reactのuseStateのsetterは遅延要素?
もしかしたら超当たり前のことかもしれませんが、少しハマったので残します。
下記リポジトリのハイライト行です。
コードは修正例の方でも理想的なコーディングをしようとしたものではありません。あくまで事象の再現のためなので、色々とマネはしないでください。
useEffect( () => { // 1. ここでリスト(data)を初期化して console.log("TEST"); setData(initialData); axios.get(API_BASEURL + "comments/" + `?userId=${selectedState.selectedUserId}`) .then((response) => { // 本来はここでループを回していちいちsetDataする必要はありません。再現のためです。 for (let i = 0; i < response.data.length; i++) { const currentData = response.data[i]; console.log([...data, { id: currentData.id, content: currentData.content }]); setData([...data, { id: currentData.id, content: currentData.content }]); // ←ココが問題 } }) }, [selectedState.selectedUserId] );
ここではuseEffect
を使ってselectedState.selectedUserId
を監視し、selectedState.selectedUserId
に変化があった時に
setData
を使ってdataを初期化(サイズ0の配列にする)- axiosのgetを使ってAPIを叩き、得られた要素全てについて
setData
を使ってdata
を追加
を行っています。
この結果、下図のようになることを期待していましたが、
実際は下図のようになってしまいます。
これの修正のためにはuseEffect()
を以下のようにすればいけました。
useEffect( () => { // 1. ここでリスト(data)を初期化して console.log("TEST"); setData(initialData); var tmp: UserComment[] = []; // ← 一時領域を確保しておく axios.get(API_BASEURL + "comments/" + `?userId=${selectedState.selectedUserId}`) .then((response) => { // 本来はここでループを回していちいちsetDataする必要はありません。再現のためです。 for (let i = 0; i < response.data.length; i++) { const currentData = response.data[i]; console.log([...data, { id: currentData.id, content: currentData.content }]); tmp = [...tmp, { id: currentData.id, content: currentData.content }]; // ← 一時領域にためていって setData(tmp); // ← ここでまとめてsetDataする(ループ内で毎回やってるけど) } }) }, [selectedState.selectedUserId] );
考察
しっかりとした仕様レベルの理解はしていません。
今回のsetData()
はuseStateで取得したsetterです。
このsetterでdataに格納した情報はワンテンポ*1遅れてdataに反映されると考えられます。
なので、ループ内の次の周で参照したdataの中身は空っぽのまま、という状態が発生しているものと考えられます。
厳密な遅延?の仕様については追って理解して書こうと思いますが、取り急ぎハマった事象をメモしました。
おまけ
ぶっちゃけ今回は以下の通り書くのが一番だと思います。本番で使うならあとはエラー処理も必要。
useEffect( () => { axios.get(API_BASEURL + "comments/" + `?userId=${selectedState.selectedUserId}`) .then((response) => { setData(response.data); }) }, [selectedState.selectedUserId] );