React レッスン4
State(useState)
コンポーネントの状態を管理しよう
Stateとは?
Stateは、コンポーネントが内部で保持する「状態」のことです。 ユーザーの操作によって変化するデータ(入力値、開閉状態、カウントなど)をStateとして管理します。
PropsとStateの違いは重要です:
- Props — 親から渡されるデータ(読み取り専用)
- State — コンポーネント自身が管理するデータ(変更可能)
Stateが変更されると、Reactはコンポーネントを自動的に再レンダリングしてUIを更新します。
useStateの基本
useState はReactのHookで、 コンポーネントに状態を追加します。
import { useState } from "react";
function Counter() {
// useState(初期値) → [現在の値, 更新関数] を返す
const [count, setCount] = useState(0);
return (
<div>
<p>カウント: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setCount(count - 1)}>-1</button>
<button onClick={() => setCount(0)}>リセット</button>
</div>
);
}
// 文字列のState
function NameInput() {
const [name, setName] = useState("");
return (
<div>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="名前を入力"
/>
<p>こんにちは、{name || "ゲスト"}さん!</p>
</div>
);
}
// 真偽値のState
function Toggle() {
const [isOn, setIsOn] = useState(false);
return (
<button onClick={() => setIsOn(!isOn)}>
{isOn ? "ON" : "OFF"}
</button>
);
}関数型更新(prev)
前の状態に基づいて更新する場合は、関数型更新を使うのが安全です。 これにより、古い状態を参照してしまうバグを防げます。
function Counter() {
const [count, setCount] = useState(0);
// NG: 連続呼び出しで問題が起きる可能性
const handleTripleAdd = () => {
setCount(count + 1); // countは古い値のまま
setCount(count + 1); // 同じ古い値を使う
setCount(count + 1); // 結果: +1 しか増えない
};
// OK: 関数型更新なら確実
const handleTripleAddCorrect = () => {
setCount(prev => prev + 1); // 最新の値を使う
setCount(prev => prev + 1); // 更新後の値を使う
setCount(prev => prev + 1); // 結果: +3 増える
};
return (
<div>
<p>カウント: {count}</p>
<button onClick={handleTripleAddCorrect}>+3</button>
</div>
);
}prev => パターンは、 前の状態に依存する更新では常に使うことが推奨されます。
オブジェクトのState
Stateにオブジェクトを持つ場合は、スプレッド構文で新しいオブジェクトを作る必要があります。 直接変更(ミューテーション)してはいけません。
function ProfileForm() {
const [user, setUser] = useState({
name: "",
email: "",
age: 0,
});
// NG: オブジェクトを直接変更してはいけない
// user.name = "太郎"; // Reactが変更を検知できない
// OK: スプレッド構文で新しいオブジェクトを作る
const updateName = (newName) => {
setUser({ ...user, name: newName });
};
// ネストしたオブジェクトの更新
const [profile, setProfile] = useState({
name: "太郎",
address: {
city: "東京",
zip: "100-0001",
},
});
const updateCity = (newCity) => {
setProfile({
...profile,
address: {
...profile.address,
city: newCity,
},
});
};
return (
<form>
<input
value={user.name}
onChange={(e) => updateName(e.target.value)}
placeholder="名前"
/>
<input
value={user.email}
onChange={(e) => setUser({ ...user, email: e.target.value })}
placeholder="メール"
/>
<p>名前: {user.name}、メール: {user.email}</p>
</form>
);
}配列のState
配列も同様に、直接変更せず新しい配列を作ることが重要です。
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: "買い物", done: false },
{ id: 2, text: "掃除", done: true },
]);
const [input, setInput] = useState("");
// 追加(スプレッド構文)
const addTodo = () => {
if (!input.trim()) return;
setTodos([...todos, {
id: Date.now(),
text: input,
done: false,
}]);
setInput("");
};
// 削除(filter)
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
// 更新(map)
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id
? { ...todo, done: !todo.done }
: todo
));
};
return (
<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="新しいTODO"
/>
<button onClick={addTodo}>追加</button>
<ul>
{todos.map(todo => (
<li key={todo.id}>
<span
onClick={() => toggleTodo(todo.id)}
style={{
textDecoration: todo.done ? "line-through" : "none",
cursor: "pointer",
}}
>
{todo.text}
</span>
<button onClick={() => deleteTodo(todo.id)}>削除</button>
</li>
))}
</ul>
</div>
);
}配列操作の鉄則:push、splice は使わず、filter、map、スプレッド構文で新しい配列を作りましょう。
まとめ
useStateでコンポーネントに状態を追加する- State更新でReactが自動再レンダリングする
- 前の状態に基づく更新は関数型更新(
prev =>)を使う - オブジェクト・配列は直接変更せず、新しいコピーを作る