状態管理
Reactの状態管理パターンを理解し、適切な方法を選択できるようになりましょう。
状態の種類
ローカル状態
コンポーネント内でのみ使う状態
- ・フォームの入力値
- ・モーダルの開閉
- ・ローディング状態
→ useState
グローバル状態
複数コンポーネントで共有する状態
- ・ログインユーザー情報
- ・テーマ設定
- ・ショッピングカート
→ Context API / Zustand
サーバー状態
APIから取得するデータ
- ・記事一覧
- ・ユーザープロフィール
- ・コメント
→ SWR / React Query
URL状態
URLに含まれる状態
- ・検索クエリ
- ・ページ番号
- ・フィルター条件
→ useSearchParams
useState(ローカル状態)
"use client";
import { useState } from "react";
export function Counter() {
// 基本的な使い方
const [count, setCount] = useState(0);
// オブジェクトの場合
const [form, setForm] = useState({
title: "",
content: "",
});
// 配列の場合
const [items, setItems] = useState<string[]>([]);
return (
<div>
<p>カウント: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setCount((prev) => prev - 1)}>-1</button>
{/* オブジェクトの更新 */}
<input
value={form.title}
onChange={(e) => setForm({ ...form, title: e.target.value })}
/>
{/* 配列への追加 */}
<button onClick={() => setItems([...items, "new"])}>追加</button>
</div>
);
}
Context API(グローバル状態)
contexts/ThemeContext.tsx
"use client";
import { createContext, useContext, useState, ReactNode } from "react";
type Theme = "light" | "dark";
type ThemeContextType = {
theme: Theme;
toggleTheme: () => void;
};
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<Theme>("light");
const toggleTheme = () => {
setTheme((prev) => (prev === "light" ? "dark" : "light"));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error("useTheme must be used within a ThemeProvider");
}
return context;
}
使用例
"use client";
import { useTheme } from "@/contexts/ThemeContext";
export function ThemeToggle() {
const { theme, toggleTheme } = useTheme();
return (
<button onClick={toggleTheme}>
現在: {theme} | クリックで切り替え
</button>
);
}
カスタムフック
hooks/useLocalStorage.ts
"use client";
import { useState, useEffect } from "react";
export function useLocalStorage<T>(key: string, initialValue: T) {
// 初期値の設定
const [storedValue, setStoredValue] = useState<T>(() => {
if (typeof window === "undefined") {
return initialValue;
}
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
// 値が変更されたらlocalStorageに保存
useEffect(() => {
try {
window.localStorage.setItem(key, JSON.stringify(storedValue));
} catch (error) {
console.error(error);
}
}, [key, storedValue]);
return [storedValue, setStoredValue] as const;
}
// 使用例
function App() {
const [name, setName] = useLocalStorage("name", "");
return (
<input value={name} onChange={(e) => setName(e.target.value)} />
);
}
状態管理の選び方
| 状況 | 推奨 | 理由 |
|---|---|---|
| 1つのコンポーネント内 | useState | シンプルで十分 |
| 親子で共有 | props | 明示的なデータフロー |
| 深い階層で共有 | Context API | バケツリレー回避 |
| 複雑なグローバル状態 | Zustand | シンプルなAPI |
| サーバーからのデータ | SWR / React Query | キャッシュ・再検証が便利 |
まとめ
- ✓ ローカル状態はuseState
- ✓ グローバル状態はContext APIまたはZustand
- ✓ サーバー状態はSWR/React Query
- ✓ 必要最小限の状態管理で始める