はじめに
Reactで以下のようなコードを書くと、初心者から上級者まで一度は「なんでこうなるの?」と混乱します。
const [status, setStatus] = React.useState("clean")
const handleClick = () => {
setStatus("dirty")
alert(status) // えっ、まだ"clean"なの?
}
ボタンをクリックするとstatus
を"dirty"
に設定したはずなのに、alert
で表示されるのは古い値の"clean"
──これにはちゃんと理由があります。この記事ではその理由と、実務での注意点や対応方法を紹介します。
Reactの状態更新は「非同期」
ReactではuseState
による状態変更は即座に反映されるわけではありません。代わりに「再描画のスケジュール」がされるだけです。これは以下のような流れです:
-
setStatus("dirty")
が呼ばれる - 状態更新がReactにキューイングされる
-
handleClick
の実行が終わったあとに再レンダリングが発生 - そのときようやく
status === "dirty"
となる
つまり、setStatus
直後にstatus
を参照してもまだ古い値のままというわけです。
実務での注意点とコツ
✅ 1. 状態変更後の処理はuseEffect
で
状態が変わった「あと」に処理を行いたい場合、useEffect
を使うのが正しいアプローチです。
useEffect(() => {
if (status === "dirty") {
alert("状態が変更されました: " + status)
}
}, [status])
✅ 2. setX
の直後にX
を使わない
ありがちなバグ:
setIsLoading(true)
console.log(isLoading) // まだfalse
isLoading
はまだ古い値のままです。こうした処理のタイミングミスは非同期通信(APIリクエスト)やUIトグルで特に起こりやすくなります。
✅ 3. 状態の変更が即時必要な場合はローカル変数や関数内で扱う
「すぐに使いたい値」は状態で持つのではなく、ローカル変数で完結することも検討しましょう。
const handleClick = () => {
const nextStatus = "dirty"
alert(nextStatus) // これは確実に"dirty"
setStatus(nextStatus)
}
✅ 4. フォームや一時的なUIは「コントロールされすぎない」ように設計する
複雑なフォームやモーダル制御で状態の変更タイミングが混乱しやすい場合、以下のような工夫をしましょう:
- 表示・非表示を制御するstateをbooleanで持つ(
isOpen
など) - フォームの一時入力状態は
useRef
やcontrolled input
で管理 - 状態変化によって動く処理は必ず副作用として
useEffect
で定義
🧪 おまけ:再現用ミニコンポーネント
export default function VibeCheck() {
const [status, setStatus] = React.useState("clean")
const handleClick = () => {
setStatus("dirty")
alert(status) // 1回目は "clean" が出る
}
return (
<button onClick={handleClick}>
{status}
</button>
)
}
💡 併せて読みたい
おわりに
Reactの状態管理は慣れれば非常に強力ですが、最初はその非同期性に戸惑うことも多いです。この記事が、「なぜ古い値が表示されるのか?」という疑問のヒントになれば幸いです。