コールバックタイプの setState を使って 同期的に複数回呼び出されていたので それだけの動きを確認するためにこんな感じのページを用意
const App = () => {
const [state, setState] = useState(0)
const onClick = () => {
console.log("ONCLICK")
console.log("pre setState(1)")
setState(prev => {
console.log("setState(1)")
return 1
})
console.log("pre setState(2)")
setState(prev => {
console.log("setState(2)")
return 2
})
console.log("pre setState(3)")
setState(prev => {
console.log("setState(3)")
return 3
})
}
return <button onClick={onClick}>{state}</button>
}
この画面を開いてボタンをクリックしたときのログは
ONCLICK
pre setState(1)
setState(1)
pre setState(2)
pre setState(3)
setState(2)
setState(3)
setState(2) ///
setState(3) ///
/// を書いてる行は React の devtools を入れてると薄く表示されるログ
もう一度ボタンを押すと少し変わって
ONCLICK
pre setState(1)
pre setState(2)
pre setState(3)
setState(1)
setState(2)
setState(3)
setState(1) ///
setState(2) ///
setState(3) ///
最初の 1 を見ると同期的に実行されてるようにみえるけど 2, 3 を見るとコールバックは即時呼び出されずあとになって呼び出されてるのがわかる
薄く表示されるのは Strict モードで 2 回レンダー関数が呼び出されるときに 2 回目の実行中に呼び出されたものだったと思うけど そんなタイミングで実行されてるの?
コンポーネントの関数の最初に console.log を入れて試してみると本当にそんなタイミングで呼び出されてた
二回目のボタンでは setState の 1 より先に 2 や 3 の pre が呼び出されているのも気になる
どっち先かは運次第のランダムかなと思ったけどリロードして試すと再現性がある
薄いところだけ無視すると一回目は 2 と 3 だけ消えて 1 は残るという変な状態になるから console.log デバッグのときは Strict モードを一時的に無効にしたほうがわかりやすいかも
ちなみに原因になったライブラリは props で受け取った値が変わったときに state を更新するために useMemo + setState するとか変なことしてるのが多くて色々辛かった
useMemo(() => {
setState(value_prop)
}, [value_prop])
みたいなの
メモの中で副作用起こさないでほしいし useEffect か ref に保持して if 文でやってほしい