[React] useStateで配列追加が反映されない? 結論、関数型の状態更新を使おう!

2023年2月7日火曜日

react

t f B! P L

タイトルの通り、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件しか表示されていない。

enter image description here

これは、冒頭で述べた通り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件表示される。
enter image description here

まとめ

Reactの useState で配列の追加を行う場合は、変更前の値を引数で受け取り状態更新を行う関数型を使おうという話でした。

スポンサーリンク
スポンサーリンク

このブログを検索

Profile

自分の写真
Webアプリエンジニア。 日々新しい技術を追い求めてブログでアウトプットしています。
プロフィール画像は、猫村ゆゆこ様に書いてもらいました。

仕事募集もしていたり、していなかったり。

QooQ