タイトルの通り、Reactの useState
で配列の追加を行う場合は、の関数型の方法で値を更新しろと言う話である。
スポンサーリンク
setStateの呼び出しは非同期である
useState()
の状態更新は非同期であり、呼び出したからと言って、すぐに対象の変数の値が更新されるわけではない。これは、useState()
の状態更新を複数呼んでも render()
は一回しか呼び出されないための仕組みだからです。
こんな処理はうまくいかない!
例えば、次の5回ループする処理で、各ループの中で setItems()
で配列に値を追加する処理は、想定した通りの動きにならない。
function Sample() {
//リストに表示する配列
const [items, setItems] = useState<number[]>([])
//リスト表示クリック時の処理
const handleClick = () => {
for (let i = 0; i < 5; i++) {
setItems([...items, i])
}
}
return (
<div>
<button onClick={handleClick}>リスト表示</button>
<ul>
{items.map(value => <li key={value}>value={value}</li>)}
</ul>
</div>
)
}
上のコードだけ見れば、計5回 setItems()
で配列に要素を追加しているので、リストにも5件表示されそうだが、実行すると画面のリストには、最後のループで配列に追加した1件しか表示されていない。
これは、冒頭で述べた通りuseState()
の状態更新は非同期であることに起因する。つまり、変数 items
はレンダリング直前まで setState()
で行った変更が反映されないため、ループ中のitems
は初期値の空の配列から変化しない。そのために、ループの中の処理では常に空の配列に対して要素を追加することになり、結果、最後で追加した要素だけがitems
に反映されることになっている。
試しに、ループの中で配列の件数をログに出してみると、この問題がすぐに理解できる。
//リスト表示クリック時の処理
const handleClick = () => {
for (let i = 0; i < 5; i++) {
console.log("i=" + i + " length=" + items.length) // ←追加
setItems([...items, i])
}
}
▼結果
i=0 length=0
i=1 length=0
i=2 length=0
i=3 length=0
i=4 length=0
スポンサーリンク
関数型のsetStateを使う
一回のオペレーションで同じ変数に対してuseState()
の状態更新を複数呼び出す場合は、変更前の値を引数に取り値を更新する関数型を更新を使うことで前述の問題に対応できる。
問題のコードを関数型の状態更新に変更したものが次のコードだ。
変更前の値(oldValue
)を引数を受け取り、スプレッド演算子で新しい値を追加した配列を作る。
//リスト表示クリック時の処理
const handleClick = () => {
for (let i = 0; i < 5; i++) {
setItems((oldValue => [...oldValue, i])) // ←変更
}
}
実行すると、想定どおりリストが5件表示される。
まとめ
Reactの useState
で配列の追加を行う場合は、変更前の値を引数で受け取り状態更新を行う関数型を使おうという話でした。
0 件のコメント:
コメントを投稿