当サイトは、アフィリエイト広告を利用しています

useStateとuseEffectの値はいつ反映されるのか?

作成日:2022月04月06日
更新日:2023年09月16日

useStateとuseEffectの処理実行順をまとめる。

useStateの実行順序

useStateを使ったサンプルを作って実行順を確認してみる

サンプル実装

react,emotionでタグを選択するサンプルを実装し、
useStateの実行順を確認する

実行環境

reactでemotionを使えるよう必要なパッケージを入れる

package.json
{
"name": "react",
"version": "1.0.0",
"description": "React example starter project",
"keywords": [
"react",
"starter"
],
"main": "src/index.js",
"dependencies": {
"@emotion/core": "11.0.0",
"@emotion/react": "11.8.2",
"@emotion/styled": "11.8.1",
"react": "18.0.0",
"react-dom": "18.0.0",
"react-scripts": "4.0.0"
},
"devDependencies": {
"@babel/runtime": "7.13.8",
"typescript": "4.1.3"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}

index.jsx

index.jsx
import { createRoot } from "react-dom/client";
import App from "./App";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(
<App />
);

App.jsx

タグ一覧を表示し、選択するとタグの色が変わるコンポーネントを実装。
要所要所にログを仕込む

App.jsx
/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";
import { useState } from "react";
export default function App() {
console.log("【関数コンポーネント実行】")
// タグ初期値
const array = Array.of(
"java",
"javascript",
"gatsby",
);
// 選択中タグのstateを定義
const [selectedTag, selectTag] = useState([]);
console.log("【useStateを実行】")
console.log(" ・選択中のタグ:",selectedTag)
// タグ選択処理(state更新)
const push = (e) => {
e.preventDefault();
selectTag(() => [e.target.id]);
console.log("【state更新(タグ選択)】")
console.log(" ・選択中のタグ:",selectedTag)
};
return (
<div className="App">
{console.log("【render(画面描画)】")}
<h2>タグ</h2>
<div css={[tagsGrid]}>
{array.map((val, i) => (
<button
key={i}
id={val}
css={[grigItem(selectedTag, val,i)]}
onClick={(e) => push(e)}
>
{val}
</button>
))}
</div>
{console.log("---------------------------------")}
</div>
);
}
// タグ選択時のスタイル変更
const grigItem = (selectTag, select,i) => {
const count = selectTag.filter((selected) => select === selected);
console.log(` ${i}.emotion関数実行(タグのスタイル設定):${count}`)
if (count.length !== 0) {
return [
css`
background-color: skyblue;
`
];
}
};
const tagsGrid = () => [
css`
display: grid;
grid-template-columns: repeat(3, minmax(100px, 10%));
grid-auto-rows: 50px;
justify-content: center;
align-items: center;
gap: 10px;
border: 1px solid black;
margin: auto;
`
];

実行してみる

作成したサンプルを初期表示から順に実行して、ログを確認する

初期表示時

log
【関数コンポーネント実行】
【useStateを実行】
・選択中のタグ: [] 
【render(画面描画)】
0.emotion関数実行(タグのスタイル設定):
1.emotion関数実行(タグのスタイル設定):
2.emotion関数実行(タグのスタイル設定):
---------------------------------

初期表示時は特に特別なことはなく

  1. 関数コンポーネント実行
  2. useStateの実行
  3. 画面描写

と上から順に実行される

タグ選択時

log
// 【関数コンポーネント実行】
// 【useStateを実行】
// ・選択中のタグ: []
// 【render(画面描画)】
// 0.emotion関数実行(タグのスタイル設定):
// 1.emotion関数実行(タグのスタイル設定):
// 2.emotion関数実行(タグのスタイル設定):
// ---------------------------------
// ↑は初期表示時のログ
【state更新(タグ選択)】
・選択中のタグ: [] //選択したタグ"java"はここではstateに未反映
【関数コンポーネント実行】
【useStateを実行】
・選択中のタグ: (1) ["java"] //選択したタグ"java"はここでstateに反映される
【render(画面描画)】
0.emotion関数実行(タグのスタイル設定):java
1.emotion関数実行(タグのスタイル設定):
2.emotion関数実行(タグのスタイル設定):
---------------------------------

初期表示からログを選択した時の動作を見てみる。

  1. タグを選択したことにより、stateが更新される
  2. 1でstateは更新されてはいるがこのタイミングではstateの値は変わっていない※[]のまま
  3. 関数コンポーネントが実行される
  4. useStateが実行されたタイミングでstateの値が更新される※["java"]になる
  5. 更新されたstateで画面が再レンダリングされる。

このログからuseStateの値が完全に更新されるのは
setStateでstateを更新された後、関数コンポーネントとuseStateが再度実行された後であることが
わかる。
その後に更新されたstateを使ってて画面を再描画している。

useStateまとめ

  • useStateは同一レンダー内(同じイベント内)では更新しても値は反映されない。
  • stateを更新した場合、関数コンポーネントとuseStateも再実行された後に再レンダリングが動く
  • stateに値が反映されるのは、stateを更新した後に再度、useStateが実行された時。

uesEffectの実行順序

useState,useEffectを使ったサンプルを作って実行順を確認してみる

サンプル実装(基本)

useStateとuseEffectを使って 単純なカウンター実装する

index.jsx

index.jsx
import { createRoot } from "react-dom/client";
import App from "./App";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(
<App />
);

App.jsx

カウントするだけのコンポーネントにログを仕込む

App.jsx
import "./styles.css";
import React, { useEffect, useState } from "react";
export default function App() {
console.log("【関数コンポーネントを実行】")
//カウンターのstate
const [count, setCount] = useState(1);
console.log("【useStateを実行】")
console.log(" ・カウント:",count)
useEffect(() => {
// 任意処理
console.log("【useEffect任意処理】")
// クリーンアップ処理
return () => {
console.log("【useEffectクリーンアップ処理】")
};
}, [count]);
// カウントアップ関数
const countUp = () => {
setCount((prevCount) => prevCount + 1);
console.log("【state更新(カウントアップ)】")
console.log(" ・カウント:",count)
};
return (
<div className="App">
{console.log("【render(画面描画)】")}
<div>
<h2>カウンター</h2>
<button onClick={countUp}>click me</button>
{count}
</div>
</div>
);
}

実行してみる

作成したサンプルを初期表示から順に実行して、ログを確認する

初期表示時

log
【関数コンポーネントを実行】
【useStateを実行】
・カウント: 1
【render(画面描画)】
【useEffect任意処理】

画面レンダー後にuseEffect処理が実行される

カウント実行時

log
// 【関数コンポーネントを実行】
// 【useStateを実行】
// ・カウント: 1
// 【render(画面描画)】
// 【useEffect任意処理】
// ↑は初期表示時のログ
【state更新(カウントアップ)】
・カウント: 1 //ここでstateの値は更新されるが未反映
【関数コンポーネントを実行】
【useStateを実行】
・カウント: 2  //ここでstateの値が2に反映される
【render(画面描画)】
【useEffectクリーンアップ処理】
【useEffect任意処理】

初期表示からカウントアップした時の動作を見てみる。

  1. カウントアップでstateが更新される※値未反映
  2. 関数コンポーネント実行
  3. useState実行で値がstateに反映される
  4. 画面描画
  5. useEffectクリーンアップ処理
  6. useEffect任意処理

useEffectは画面描画後に動き、さらにuseEffectのクリーンアップ処理は
任意処理よりも先に動くことがわかる。

サンプル実装(応用)

useStateとuseEffectを使って カウンターとカウンターログを表示するサンプルを実装する
※useEffect内でuseStateを更新するパターン

index.jsx

index.jsx
import { createRoot } from "react-dom/client";
import App from "./App";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(
<App />
);

App.jsx

カウンターボタンとカウント数、カウンターログを表示するコンポーネントを実装し、
ログを仕込む

App.jsx
import "./styles.css";
import React, { useEffect, useState } from "react";
export default function App() {
console.log("【関数コンポーネントを実行】")
//カウンターのstate
const [count, setCount] = useState(1);
//ログーのstate
const [log, setLog] = useState([]);
console.log("【useStateを実行】")
console.log(" ・カウント:",count)
console.log(" ・ログ:",log)
// useEffectでcountに変更が合った場合のみlog出力する.
useEffect(() => {
// ログを表示する
setLog((prevLog) => [...prevLog, `count${count}`]);
console.log("【useEffect任意処理】")
console.log(" ・ログ:",log)
// クリーンアップ処理
// countに変更があった場合に実行される
return () => {
// ログが4行になったら初期化
setLog((prevLog) =>
prevLog.length === 4 ? [] : prevLog.map((log) => log)
);
// カウントが4になったら初期化
setCount((prevCount) => (prevCount === 4 ? 1 : prevCount));
console.log("【useEffectクリーンアップ処理】")
};
}, [count]);
// カウントアップ関数
const countUp = () => {
setCount((prevCount) => prevCount + 1);
};
return (
<div className="App">
{console.log("【render(画面描画)】")}
<div>
<h2>カウンター</h2>
<button onClick={countUp}>click me</button>
{count}
</div>
<div>
<h2>log</h2>
{log.map((log) => (
<div key={log}>{log}</div>
))}
</div>
</div>
);
}

実行してみる

作成したサンプルを初期表示から順に実行して、ログを確認する

初期表示時

log
【関数コンポーネントを実行】
【useStateを実行】
・カウント: 1
・ログ: []
【render(画面描画)】
【useEffect任意処理】
・ログ: [] // stateのlogを更新するがここでは未反映、stateを更新したため再レンダリングが動く
【関数コンポーネントを実行】
【useStateを実行】
・カウント: 1
・ログ: (1) ["count1"] // stateのlogをここでは反映される
【render(画面描画)】

初期表示時は下記のような動きになる

  1. 関数コンポーネント実行
  2. useStateの実行(初期値設定)
  3. 画面描写
  4. useEffect実行(useStateに初期値が入ったため)
  5. 4でログは更新されるがここでは[]のまま
  6. 関数コンポーネント実行
  7. useStateの実行(useEffectで設定した値が反映される)
  8. 画面描写

useEffectが動くのは一度画面描写された後になる。
またuseStateの時と同じでuseEffect内で更新されたstateはこのタイミングでは反映されず
次のuseState実行で反映されることがわかる。
※クリーンアップ処理は初期表示時は実行されない

カウント実行時

log
// 【関数コンポーネントを実行】
// 【useStateを実行】
// ・カウント: 1
// ・ログ: []
// 【render(画面描画)】
// 【useEffect任意処理】
// ・ログ: []
// 【関数コンポーネントを実行】
// 【useStateを実行】
// ・カウント: 1
// ・ログ: (1) ["count1"]
// 【render(画面描画)】
// ↑は初期表示時のログ
【state更新(カウントアップ)】
・カウント: 1 // stateのcountを更新するがここでは未反映
【関数コンポーネントを実行】
【useStateを実行】
・カウント: 2 // stateのcountの更新がここで反映される
・ログ: (1) ["count1"]
【render(画面描画)】
【useEffectクリーンアップ処理】
【useEffect任意処理】
・ログ: (1) ["count1"] // stateのlogを更新するがここでは未反映、stateを更新したため再レンダリングが動く
【関数コンポーネントを実行】
【useStateを実行】
・カウント: 2
・ログ: (2) ["count1", "count2"] // stateのlogの更新がここで反映される
【render(画面描画)】

初期表示からカウントアップした時の動作を見てみる。

  1. カウントアップしたことによりstate:countが更新される
  2. 1でstate:countは2に更新されてはいるがこのタイミングではstate:countの値は変わっていない※"1"のまま
  3. 関数コンポーネントが実行される
  4. useStateが実行されたタイミングでstate:countの値が更新される※"2"になる
  5. 【render(画面描画)】
  6. 【useEffectクリーンアップ処理】
  7. 【useEffect任意処理】 でstate:logの値を["count1", "count2"]にするがここでは反映されない。
  8. 【関数コンポーネントを実行】
  9. 【useStateを実行】 でstate:logの値が["count1", "count2"]になる
  10. 【render(画面描画)】

大分、ややこしくなってしまったが要点としては stateを更新した値が完全に反映されるのは次のuseStateかuseEffectのタイミングになる。
ということ。

useEffectまとめ

  • useEffectは同一レンダー内(同じイベント内)では更新しても値は反映されない。
  • useEffectは画面描画後に動く。
  • クリーンアップ処理は初回は動かない
  • クリーンアップ処理(初回以外)は任意処理より先に動く
  • useEffectが動くだけでは画面は再描画されない
  • useEffect内でstateを更新した場合は再描画が動く。

要点

要点としては下記になると思います

  • stateはstateを更新した同一レンダー内では更新されない。
    ※同一レンダーとは関数コンポーネントが呼ばれてから次に再度、関数コンポーネントが呼ばれるまでの間のこと
  • useEffectも同様で同一レンダー内では値を更新しても反映されない
  • 次のレンダー時(再度、関数コンポーネント呼ばれた時)に反映される
    ※厳密にはuseState実行時に反映される

参考

下記を参考にさせて頂きました
Reactでステートの値が反映されない時に試すこととその理由

新着記事

タグ別一覧
top