はじめに
この記事はJSL(日本システム技研) Advent Calendar 2021の記事です。
Reactで「なんかStateの値が意図したのもと違うんだけど!?」と思った時に読んでほしい記事になります。
1年目のペーペーですが、少しでもuseStateの挙動の理解の助けになれば幸いです。
そのStateの更新関数、複数回呼ばれていないですか?
結論から言いますと、一回の処理の中で複数回Stateの更新関数がよばれているため、Stateの値が意図した値と異なっている可能性があります。
なぜ複数回Stateの更新関数が呼ばれるとStateの値が意図したものと異なってしまうのでしょうか?
というのをもったいぶって以下で説明します。(この章いらんかったかもしれない)
Stateの更新関数が複数回呼ばれた時の挙動
例として、Reactを学んだことがある人なら1度は見たことがあるようなありがちなサンプルで説明します。
こんな感じのボタンを押すと画面に表示されている数字がインクリメントするだけのサンプルです。
コードはこんな感じ
import { useState } from 'react'
const Conponent = () => {
const [number, setNumber] = useState(1)
const countUp = () => setNumber(number + 1)
return (
<div>
<button onClick={countUp}>+1</button>
<h1>{number}</h1>
</div>
)
}
export default Conponent
仮にこのサンプルで、一度ボタンを押しただけで、numberというStateを、2ずつ、3ずつ増やしたいとした時、みなさんならどうしますでしょうか?
下のコードような感じの例を思いついた人が多いのかなと思います。
しかし、このうち例1は正常に動作するものの、例2は正常に動作しません。
// 例1 こちらはボタンを押すと2ずつインクリメントしてくれる
const countUp = () => setNumber(number + 2)
// 例2 こちらはボタンを押しても1ずつしかインクリメントされない
const countUp = () => {
setNumber(number + 1)
setNumber(number + 1)
}
下のGIFを見ると、例2の場合、countUpの中で3回Stateの更新関数(setNumber)を呼んでいるにもかかわらず1ずつしか増えないことがわかると思います。
setNumberを3回実行しても、現在のnumberの値に1を足すことを3回やるだけなので、最終的な更新結果は現在のnumberに1を加えただけということです。(説明難しいですが、最後のsetNumberの処理だけ生きるみたいなイメージ持ってもらえると)
Stateを関数の戻り値で更新する
今回のようなただ特定の数字を増やすようなケースであれば、例1のようなコードを書けば済む話かとは思いますが、実際に複数回Stateの更新関数を呼び、前回値を使いたいときはどうすれば良いのか、、、?
そういったニーズに応えるべく、useStateのStateの更新関数には、引数に関数を取ることができ、その関数の第一引数に前回値を保持するという機能があります。(その関数の戻り値が更新する値になります。)
const countUp = () => {
setNumber(number + 1)
// preの中に前回値(このケースだと上のsetNumberで+1された値)が入る
setNumber((pre)=>pre + 1)
}
このコードであれば1回目のsetNumberで更新した値を、preが保持してくれているので、一回目のsetNumberでnumber+1
、2回目のsetNumberで(number+1)+1
のような処理になり、countUpを1度呼ぶだけでnumberが2ずつ増えてくれます。
以下のGIFのようなな感じで今度は3ずつ増やせています。(ボタン表示が+1のままでややこしいですが)
このように複数回Stateの更新関数を呼びたい(かつ前回値を使いたい)ときは、関数の戻り値で更新するようにしましょう。
これが意外とバグの原因になりうる
タイトルが「ReactでStateの更新がうまくいかないなと思った時に疑ってみて欲しいこと」であるにもかかわらず、これまで、useStateの便利機能紹介みたいな記事になっておりすいません。
この複数回Stateの更新関数が走った時の挙動がらみで、アプリ開発中、意図したデータが返ってこなかったり、意図した表示にならなかったりすることがあるので注意しましょうということが言いたかったわけです。
私自身、Stateの更新をしているのに、正常に更新できていないみたいなケースで、調べてみると、実は遠いところで複数回にわたり更新関数が呼ばれていたりすることは何度かありました。(色んなところで別々にStateを更新していると起きやすい)
また、意図して複数回Stateの更新関数を使う時も、このuseStateの前回値保持の機能を知らないと、最後の更新関数だけ生きてその前の更新関数の処理は無かったことになってしまうので気をつけましょう。
おわりに
JSL(日本システム技研) Advent Calendar 2021
先輩方の良記事が見れますので、興味あったら覗いてみてください。
という宣伝だけ最後にしておきます。
括弧書き満載の拙い記事でしたがお読みいただきありがとうございました。