本記事は内容の訂正のため2024/7/29に更新しました。
「state 更新 反映されない」を検索するまで
Reactにまだ慣れていない段階で開発をしているとuseStateの問題に詰まってこのような検索をする方は多いかなと思います。
私もその一人で、公式のチュートリアルを一通りやったにも関わらずこの問題に詰まってしまったのは忸怩たる思いでございます。
「useStateで定義した値はset関数で更新できて、useStateはフックなので実行すると画面が更新される」というのはまずReactを勉強する最初の段階で理解するでしょう。
そこで、言われた通りにuseStateで値を定義し、ユーザーの操作によって画面を更新させるコードを書きます。
import { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
setNumber(number + 1); // numberに1を足して更新
console.log("number", number); // numberの値をコンソールに出力
}}>+1する</button>
</>
)
}
こちらはとても単純なコードで、numberというstetaを定義し、「+1する」というボタンをクリックするとsetNumber(number + 1);
でnumberの値を一つ増やしてその値をconsole.log
で出力しようというものです。
すでにReactを理解している方ならわかると思いますが、最初にconsole.log
で出力されるnumberの値は 0 になります。
ここで「え、setNumber(number + 1)
でnumberは+1
されたんじゃないの?なんで0のままになってるんだ?」となった方が
「react state 更新 反映されない」
と検索するわけです。はい、検索しました。
結果: 公式のチュートリアルに助けられた
一度チュートリアルは学習していたので、改めてuseStateに関する内容を確認したとき、すぐに思い出してこの現象の原因を理解できました。
なので stateが更新されない問題に関する理解と対処法については、結論、チュートリアルのこちらの章を読んでください。
チュートリアルで丁寧に解説されておりますので、原因や対処法について本記事ではあえて説明いたしません。
ポイントは
- 「スナップショット」が意味するもの
-
レンダーのトリガ
の二つです。
誤った情報が多く見受けられた
しかしこのチュートリアルにたどり着くまで、いくつかの誤った情報や誤解に翻弄されました。
それに関してはこちらの記事でも紹介されており、この記事を読んだ時にそれらが誤った情報であると確信を持つことができました。(感謝いたします。)
(↓※正しい記事)
私が実際に確認した誤った情報を紹介します。
①誤った理解「stateの更新は非同期で実行されるから反映が遅れる」
こちらは上記の記事でも言及されていますが、「set関数によるstateの更新は非同期で実行されるから、更新はすぐには反映されないんだよ」的な記事が見受けられました。
もし更新が非同期で実行されることが原因なのであれば、更新後にsetTimeout
などで少し待機してからログ出力をすれば更新された値が出力されるはずですが、試してみても実際そうはなりません。
const [number, setNumber] = useState(0);
...
<button onClick={() => {
setNumber(number + 1); // 0+1でnumberを1に更新 → レンダリングをトリガー!
// 非同期の更新が完了するのを待機してみても結果は同じ
// setTimeout(() => {
// console.log(`5秒後: ${number}`);
//}, 5000);
console.log("number", number); // number 0 が出力される →「1じゃないの?」
}}>+1する</button>
...
更新が反映されないように見えるのは、onclick
ハンドラとして実行されるコールバック関数内では、関数実行開始時のstateを参照するためです。その関数内で実行されたものである限り、更新後にいくら待機しても前の値が参照されます。
②誤った理解「関数が呼び出された後にstateが更新されるから更新がすぐに反映されない」
Reactはイベントハンドラでのstateの更新を、最後にバッチ処理でまとめて行う という事実があります。
公式ドキュメントを見ると、イベントハンドラに渡されたコールバック関数内で仮に複数回stateの更新を行なったとしてもその都度更新処理が走るのではなく、バッチ処理として最後にまとめて行われると述べられています。
これだけを読むと、誰もが「コールバック関数の最後にstateがまとめて更新されるから、途中のログ出力時にはまだ反映されていないんだ」と思うはずです。
しかし、実際のところは「stateの更新が反映されていない」という現象と直接は関係ありません。
これも結局同じ説明になりますが、onclick
ハンドラに渡されているコールバック関数内では、関数実行開始時のstateを参照しているというだけです。
まとめ
誤った情報が悪いのではなく、ちゃんと公式のドキュメントを確認したり、複数の情報を集めることが大切だよということを学びましたし、それをお伝えしたいという思いで本記事を作成しました。
というか、私自身、とある技術に関して誤解をしたまま記事にして公開してしまったこともあります。
そのときはかなり反省しましたが、私としては「学んだことはアウトプットして知識を定着させよう」くらいのノリで記事を書いていますので、逆に記事を参照するときは「一般人が書いたネットの記事なんてそんなもんよな」と思いながら情報を集めることができるようになりました。