useState 完全入門【React】最初に覚えるべき状態管理フック
ReactのuseStateとは何か、state変数の作り方・更新関数の使い方・オブジェクトの扱い方を初心者向けに丁寧に解説。よくあるミスとその解決策も紹介します。
Reactを学び始めたとき、最初にぶつかる概念が「state(状態)」です。そして state を扱うためのフックが useState です。
「ボタンをクリックしたら数が増える」「入力した文字が表示される」——こういったインタラクティブなUIを作るために useState は欠かせません。React の全フックの中で最も使う機会が多い、まさに基本中の基本です。
この記事でわかること:
- state とは何か・なぜ必要か
useStateの基本的な書き方- 更新関数の正しい使い方(直接更新 vs 関数形式)
- オブジェクト・配列の state を更新するときの注意点
- よくあるミスと解決策
state とは?
state は、**コンポーネントが持つ「記憶」**です。
ユーザーの操作によって変化する値(クリック回数・入力テキスト・表示/非表示のフラグなど)を state で管理します。state が変わると、React は自動でコンポーネントを再レンダリングし、画面を最新の状態に更新します。
// state なし:ボタンをクリックしても count は変わらない
function BadCounter() {
let count = 0; // ← ただのローカル変数
return <button onClick={() => count++}>{count}</button>;
}
// state あり:クリックするたびに画面が更新される
function GoodCounter() {
const [count, setCount] = useState(0); // ← state
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}基本的な書き方
const [state, setState] = useState(initialState);| 要素 | 説明 |
|---|---|
state | 現在の値 |
setState | state を更新する関数 |
initialState | 初期値(数値・文字列・配列・オブジェクトなど何でも可) |
慣例として [something, setSomething] という命名が使われます。
import { useState } from "react";
function LikeButton() {
const [liked, setLiked] = useState(false); // 初期値は false
return (
<button onClick={() => setLiked(!liked)}>
{liked ? "❤️ いいね済み" : "🤍 いいねする"}
</button>
);
}更新関数の2つの書き方
直接値を渡す
setCount(5); // count を 5 にする
setName("太郎"); // name を "太郎" にする関数形式(前の値から計算する場合)
setCount(prev => prev + 1); // 前の値に1を足す複数の更新が同時に発生する場面や、前の値を確実に参照したい場面では関数形式を使いましょう。
// ❌ 問題が起きやすい書き方
function handleClick() {
setCount(count + 1);
setCount(count + 1); // count がまだ古い値なので結果は +1 になってしまう
}
// ✅ 安全な書き方
function handleClick() {
setCount(prev => prev + 1);
setCount(prev => prev + 1); // 確実に +2 になる
}初期値を関数で計算する
計算コストが高い初期値の場合、関数として渡す(遅延初期化)と効率的です。
// ❌ 毎回のレンダリングで createInitialState() が実行される
const [state, setState] = useState(createInitialState());
// ✅ 初回レンダリングのときだけ createInitialState が実行される
const [state, setState] = useState(createInitialState); // 関数自体を渡すオブジェクトの state を更新する
オブジェクトを state にする場合、直接書き換えてはいけません。必ず新しいオブジェクトを作って渡します。
const [user, setUser] = useState({ name: "山田", age: 25 });
// ❌ NG:直接書き換えると React が変化を検知できない
user.name = "田中";
setUser(user);
// ✅ OK:スプレッド演算子で新しいオブジェクトを作る
setUser({ ...user, name: "田中" }); // nameだけ変えて他はそのまま配列の state を更新する
配列も同様に、元の配列を書き換えずに新しい配列を作ります。
const [items, setItems] = useState(["りんご", "みかん"]);
// ✅ 追加:スプレッドで新しい配列を作る
setItems([...items, "ぶどう"]);
// ✅ 削除:filterで新しい配列を作る
setItems(items.filter(item => item !== "みかん"));
// ✅ 更新:mapで新しい配列を作る
setItems(items.map(item => item === "りんご" ? "青りんご" : item));
// ❌ NG:push/splice などは使わない(元の配列を書き換えてしまう)
items.push("ぶどう");
setItems(items);よくあるミスと解決策
ミス1:更新直後に新しい値を参照しようとする
// ❌ 更新はすぐには反映されない
setCount(count + 1);
console.log(count); // まだ古い値が表示される
// ✅ 更新後の値を使いたい場合は変数に入れる
const newCount = count + 1;
setCount(newCount);
console.log(newCount); // 正しい値ミス2:オブジェクトを直接変更してから set する
// ❌ NG
const newUser = user;
newUser.name = "田中"; // 同じオブジェクトを変更してしまっている
setUser(newUser); // React が変化を検知できず画面が更新されない
// ✅ OK
setUser({ ...user, name: "田中" }); // 新しいオブジェクトを作るミス3:ループや条件分岐の中で使う
// ❌ NG:Hooksのルール違反
if (isLoggedIn) {
const [name, setName] = useState(""); // エラーになる
}
// ✅ OK:常にトップレベルで呼び出す
const [name, setName] = useState("");
if (!isLoggedIn) return null;まとめ
useStateはコンポーネントに「記憶」を持たせるフック[state, setState]の形で分割代入して使う- 前の値を使って更新するときは
setState(prev => ...)の関数形式を使う - オブジェクト・配列は直接変更せず、新しいものを作って渡す
- 更新は非同期なので、直後に新しい値を参照することはできない