🔖

React の setState はどのタイミングで更新されるのか

2024/06/28に公開
2

はじめに

ReactのuseStateを勉強中、setStateの更新タイミングについて理解が曖昧な箇所があったため調べながらまとめました。

例として以下のようなコードがあるとします。

import React, { useState, useEffect } from 'react';

const FormExample = () => {
  const [inputValue, setInputValue] = useState('');

  const handleInputChange = (event) => {
    setInputValue(event.target.value);
    // この時点ではinputValueは更新されていないので、前の値が出力される
    console.log(`handleInputChange 内のinputValue: ${inputValue}`);
  }

  const handleSubmit = () => {
    // この時点でもinputValueは最新の値にはなっていない
    console.log(`handleSubmit 内のinputValue: ${inputValue}`);
  }

  useEffect(() => {
    // 再レンダリング後にinputValueが更新されたことを検知してから実行されるため、最新のinputValueが出力される
    console.log(`useEffect内のinputValue: ${inputValue}`);
  }, [inputValue]);

  return (
    <>
      <input
        type="text"
        value={inputValue}
        onChange={handleInputChange}
      />
      <button onClick={handleSubmit}>
        送信
      </button>
    </>
  );
}

export default FormExample;

動作の流れ

初期レンダリング

inputValue は空の文字列 '' で初期化されます。
初期レンダリング時には useEffect フックが実行され、初期値である空の文字列がログに出力されます。

入力フィールドに入力

ユーザーが入力フィールドに文字を入力すると handleInputChange 関数が呼び出されます。
handleInputChange 内の setInputValue により inputValue が更新されますが、この時点ではコンポーネントの再レンダリングが完了していないため、console.log は古い inputValue を出力します。
これは、Reactの状態更新が非同期で行われるためです。
重要な点は、handleInputChange 内での inputValue は関数が最初に呼ばれたときのクロージャによりキャプチャされているため、時間が経過してもその時点の値のままです。

ボタンクリック

ユーザーが「送信」ボタンをクリックすると handleSubmit 関数が呼び出されます。
この時点でも inputValue は最新の値に更新されていないため、console.log は古い値を出力します。

再レンダリングと useEffect

inputValue が変更されたことで、状態の変更によりコンポーネントが再レンダリングされます。
再レンダリング後に useEffect フックが実行され、最新の inputValue が出力されます。

まとめ

setState の状態は非同期で更新されるため、状態の更新直後に関数内で console.log を実行しても最新の値は反映されません。再レンダリング後に実行される useEffect フックで初めて最新の状態を取得できます。

Discussion

Honey32Honey32

失礼します。

  const handleInputChange = (event) => {
    setInputValue(event.target.value);
    console.log(`handleInputChange 内のinputValue: ${inputValue}`);
  }

のコードで、更新前の値が表示される理由は、タイミングのせいではありません。handleInputChange の関数の中では、時間経過に関係なく、ずっと inpnutValue の値は更新前の値のままになります。

setTimeoutを使ってわざと 10 秒待ってから console.log() に出力してみてください。更新前の値が表示されます。

https://qiita.com/honey32/items/ee8d1577e68b0d58678d


本題からズレますが、

  • 初期レンダリング:
    • inputValue は空の文字列 '' で初期化されます。
    • 初期レンダリング時には useEffect フックは実行されません。

とありますが、これも事実と異なっています。

(依存配列を省略していない)useEffect に渡した関数は、初回レンダリング時に実行されたあと、再レンダリング時に(依存配列に列挙した値に差分がある場合のみ)実行されます。

https://zenn.dev/yumemi_inc/articles/react-effect-simply-explained

Syumai3Syumai3

ご指摘くださりありがとうございます。
大変勉強になりました。