今回はusestateでよく用いられるprevStateについてまとめました。非同期で入力を維持しながら表示できるので、Reactの良さをより体感でき気付きも多かったのでシェアいたします。
useStateの更新関数には、こんな書き方があります。
setCount(prev => prev + 1);
最初は「なぜわざわざ関数にするの?」と思いました。
prevStateがなぜ必要なのか、特にフォーム実装でどう使うのかをまとめます。
💡prevStateとは
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const incrementCount = () => {
setCount(count + 1)
setCount(count + 1)
setCount(count + 1)
setCount(count + 1)
}
return (
<>
<p>現在のカウント: {count}</p>
<button onClick={incrementCount}>カウントを増やす</button>
</>
);
}
setCountが複数あるのでそれに応じて増えるように見えますが、実際は古い状態のcountを参照するのでカウントは1ずつしか増えません。
prevStateを使うことで、状態を更新する前の状態を引数として受け取るので正しい状態を維持できます。
setState(prev => 新しい値を返す);
import { useState } from 'react';
function Precounter() {
const [count, setCount] = useState(0);
const incrementCount = () => {
setCount((prevState) => prevState + 1)
setCount((prevState) => prevState + 1)
setCount((prevState) => prevState + 1)
setCount((prevState) => prevState + 1)
}
return (
<>
<p>現在のカウント: {count}</p>
<button onClick={incrementCount}>カウントを増やす</button>
</>
);
}
ReactのsetStateは非同期で処理されます。連続して呼び出したとき、state変数が古い値を参照してしまうことがあるのでprevStateを使うと「直前の確実な値」を基に更新できるので、こうしたバグを防げます。
👉️またprevという名前は慣習で、cやstateなど何でも動作は同じです。
setCount(prev => prev + 1); // prevという名前
setCount(c => c + 1); // cでも同じ
setCount(x => x + 1); // xでも同じ
💡フォームの部分更新パターン
実務で最もよく使う場面がこれです。
import { useState } from 'react';
function Prev (){
const [form, setForm] = useState({
name: '',
email: '',
age: '',
role: ''
});
const handleChange = (e) => {
/* inputで入力したものをprevで<p>に維持 */
setForm(prev => ({
...prev,
[e.target.name]: e.target.value
}));
};
return (
<div>
<input name="name" onChange={handleChange} placeholder="名前" />
<input name="email" onChange={handleChange} placeholder="メール" />
<input name="age" onChange={handleChange} placeholder="年齢" />
<select name="role" onChange={handleChange}>
<option value="">選択</option>
<option value="フロントエンド">フロントエンド</option>
<option value="バックエンド">バックエンド</option>
</select>
{/* stateの中身を画面に出す */}
<p>name: {form.name}</p>
<p>email: {form.email}</p>
<p>age: {form.age}</p>
<p>role: {form.role}</p>
</div>
);
}
export default Prev
💡コードの3つのポイント
1. prev =>
直前のstateを確実に受け取ります。非同期の更新でも古い値を参照しません。
2. ...prev
他のフィールドを全部コピーして保持します。これがないと、1つのフィールドを入力した瞬間に他が消えてしまいます。
3. [e.target.name]
name="email" の inputなら form.email を更新、name="age" なら form.age を更新、という動的なキー指定です。
✅️prevなしだとどうなるか
// ❌ prevなし
const handleChange = (e) => {
setForm({ [e.target.name]: e.target.value });
};
名前を入力した瞬間に email、age、role が全部 "" に戻ります。
...prev がないので、入力した1フィールド以外が上書きされてしまうからです。
❗️prevStateが活躍する場面まとめ
| 場面 | 必要度 |
|---|---|
| フォームの部分更新 | ⭐⭐⭐ ほぼ必須 |
| トグル(開閉・チェック) | ⭐⭐⭐ ほぼ必須 |
| リスト追加・削除 | ⭐⭐⭐ ほぼ必須 |
| カウンター(連打あり) | ⭐⭐ あった方が安全 |
| カウンター(単純) | ⭐ なくても動く |
📖まとめ
-prevStateを使うと「直前の確実な値」を基に更新できる
-
prevの名前は自由。「直前のstateを受け取って新しい値を返す」形が大事 - フォームでは
...prevでスプレッドして、1フィールドだけ書き換えるのが定番 - stateを画面に出す習慣がデバッグの基本
フォーム・トグル・リスト操作と、実務で毎日使うパターンなのでしっかり手に染み込ませておきたいです。