Part 4 · 状態 (state)
state は「その瞬間の写真」
setState した直後に値が変わっていない理由
setCount を呼んだ直後に count を見ると、まだ古い値のままです。
これは不具合ではなく、React の設計です。 ただ、知らないと必ず引っかかります。 「3 回呼んだのに 1 しか増えない」という形で現れます。
3 回呼んでも 1 しか増えない
setCount(count + 1) を 3 回並べれば 3 増えそうです。 押してみてください。
1 ずつしか増えない
1 しか増えません。理由は、この関数の実行中、count がずっと同じ値だからです。
// count が 0 のときに押すと…
setCount(count + 1); // setCount(0 + 1) → 1 にして、と依頼
setCount(count + 1); // setCount(0 + 1) → やっぱり 1 にして
setCount(count + 1); // setCount(0 + 1) → やっぱり 1 にして3 回とも「1 にしてください」と頼んでいます。 結果が 1 になるのは当然です。
snapshot.tsx
"use client";
import { Button } from "@/components/ui/button";
import { useState } from "react";
export function Snapshot() {
const [count, setCount] = useState(0);
const addThree = () => {
// 3 回呼んでいるが、この関数の中では count はずっと同じ値のまま。
// 3 回とも「0 + 1」を依頼していることになる
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
};
return (
<div className="flex items-center gap-3">
<span className="font-mono text-lg font-semibold tabular-nums">
{count}
</span>
<Button size="sm" onClick={addThree}>
3 増やすつもり
</Button>
<Button size="sm" variant="outline" onClick={() => setCount(0)}>
リセット
</Button>
</div>
);
}state は「その回の写真」
前の章でやったとおり、コンポーネントは state が変わるたびに実行し直されます。
そして1 回の実行の間、state の値は変わりません。 その実行が始まった時点の値のまま、最後まで固定されています。 撮った写真が途中で変化しないのと同じです。
setCount は「次はこの値で実行してください」という予約です。いま動いている実行には影響しません。
snapshot.tsx
"use client";
import { Button } from "@/components/ui/button";
import { useState } from "react";
export function Snapshot() {
const [count, setCount] = useState(0);
const addThree = () => {
// 3 回呼んでいるが、この関数の中では count はずっと同じ値のまま。
// 3 回とも「0 + 1」を依頼していることになる
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
};
return (
<div className="flex items-center gap-3">
<span className="font-mono text-lg font-semibold tabular-nums">
{count}
</span>
<Button size="sm" onClick={addThree}>
3 増やすつもり
</Button>
<Button size="sm" variant="outline" onClick={() => setCount(0)}>
リセット
</Button>
</div>
);
}いまの値を使いたいときは関数を渡す
「1 増やす」のようにいまの値をもとに決めたいときは、 値ではなく関数を渡します。
setCount((current) => current + 1);こう書くと、React は更新するときにそのときの最新の値を current に入れて呼びます。 自分で値を計算しないので、古い値を使ってしまう心配がありません。
今度はちゃんと 3 増える
React は依頼を順番に適用します。 0 に対して +1 して 1、その 1 に +1 して 2、その 2 に +1 して 3。積み上がるので、3 増えます。
どちらを使えばいいか
- いまの値を使うなら関数を渡す(+1、切り替え、追加など)
- 関係のない値を入れるなら、そのまま渡してよい(リセット、入力値の反映など)
迷ったら関数を渡す形にしておけば、まず間違いません。
updater.tsx
"use client";
import { Button } from "@/components/ui/button";
import { useState } from "react";
export function Updater() {
const [count, setCount] = useState(0);
const addThree = () => {
// 値ではなく「いまの値をどう変えるか」を渡す。
// React が順番に適用するので、3 回ぶんが積み上がる
setCount((current) => current + 1);
setCount((current) => current + 1);
setCount((current) => current + 1);
};
return (
<div className="flex items-center gap-3">
<span className="font-mono text-lg font-semibold tabular-nums">
{count}
</span>
<Button size="sm" onClick={addThree}>
3 増やす
</Button>
<Button size="sm" variant="outline" onClick={() => setCount(0)}>
リセット
</Button>
</div>
);
}直後に読んでも変わっていない
const handleClick = () => {
setCount(count + 1);
console.log(count); // まだ古い値が出る
};「更新に時間がかかっているのでは」と思うかもしれませんが、違います。この実行における count は、最初から最後まで同じ値だからです。 待っても変わりません。
新しい値を使いたい処理があるなら、 その値を変数に入れておくか、次の実行で扱います。
const next = count + 1;
setCount(next);
console.log(next); // これなら新しい値snapshot.tsx
"use client";
import { Button } from "@/components/ui/button";
import { useState } from "react";
export function Snapshot() {
const [count, setCount] = useState(0);
const addThree = () => {
// 3 回呼んでいるが、この関数の中では count はずっと同じ値のまま。
// 3 回とも「0 + 1」を依頼していることになる
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
};
return (
<div className="flex items-center gap-3">
<span className="font-mono text-lg font-semibold tabular-nums">
{count}
</span>
<Button size="sm" onClick={addThree}>
3 増やすつもり
</Button>
<Button size="sm" variant="outline" onClick={() => setCount(0)}>
リセット
</Button>
</div>
);
}理解できたか確かめる
確認クイズ
setCount(count + 1) を 3 回並べると、count はいくつ増える?
確認クイズ
setCount((c) => c + 1) と書く利点は?
updater.tsx
"use client";
import { Button } from "@/components/ui/button";
import { useState } from "react";
export function Updater() {
const [count, setCount] = useState(0);
const addThree = () => {
// 値ではなく「いまの値をどう変えるか」を渡す。
// React が順番に適用するので、3 回ぶんが積み上がる
setCount((current) => current + 1);
setCount((current) => current + 1);
setCount((current) => current + 1);
};
return (
<div className="flex items-center gap-3">
<span className="font-mono text-lg font-semibold tabular-nums">
{count}
</span>
<Button size="sm" onClick={addThree}>
3 増やす
</Button>
<Button size="sm" variant="outline" onClick={() => setCount(0)}>
リセット
</Button>
</div>
);
}この章のまとめ
- 1 回の実行の間、state の値は固定されている
- だから
setCountの直後に読んでも古い値のまま - 値を渡す形で複数回呼んでも積み上がらない
- いまの値をもとに決めたいときは
setCount((c) => c + 1)と関数を渡す
updater.tsx
"use client";
import { Button } from "@/components/ui/button";
import { useState } from "react";
export function Updater() {
const [count, setCount] = useState(0);
const addThree = () => {
// 値ではなく「いまの値をどう変えるか」を渡す。
// React が順番に適用するので、3 回ぶんが積み上がる
setCount((current) => current + 1);
setCount((current) => current + 1);
setCount((current) => current + 1);
};
return (
<div className="flex items-center gap-3">
<span className="font-mono text-lg font-semibold tabular-nums">
{count}
</span>
<Button size="sm" onClick={addThree}>
3 増やす
</Button>
<Button size="sm" variant="outline" onClick={() => setCount(0)}>
リセット
</Button>
</div>
);
}