発行:
更新:

状態管理 (State)

Webアプリが「アプリ」らしく動くために欠かせないのが 「状態 (State)」 です。
このページでは、React/Next.js における状態管理の基本 useState について解説します。

状態(State)とは?

コンポーネントが「覚えておくべき情報」のことです。
例えば、「入力フォームに入力中の文字」、「カウントアップの数字」、「モーダルが開いているかどうか」などが状態にあたります。

変数が「ただの入れ物」だとすれば、State は「 変更されると画面(UI)を自動的に更新する入れ物 」と言えます。

基本的な使い方 (useState)

Next.js (App Router) で State を使う場合は、必ず Client Component ("use client") にする必要があります。
Server Component は「サーバーで一度だけ描画される」ものなので、ユーザーの操作に応じて変化する State を持つことができません。

カウンターを作ってみよう

クリックすると数字が増えるボタンを作ってみましょう。

src/app/counter/page.tsx
"use client"; // 必須!

import { useState } from "react";

export default function CounterPage() {
  // [今の値, 値を更新する関数] = useState(初期値)
  const [count, setCount] = useState(0);

  return (
    <main className="p-8">
      <h1 className="text-2xl font-bold mb-4">カウンター</h1>
      
      <p className="text-xl mb-4">現在のカウント: {count}</p>
      
      <button
        onClick={() => setCount(count + 1)}
        className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
      >
        増やす
      </button>
      
      <button
        onClick={() => setCount(0)}
        className="ml-4 bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600"
      >
        リセット
      </button>
    </main>
  );
}

フォームの入力値を管理する(オブジェクトの状態)

複数の入力項目がある場合、個別に useState を作ると大変です。オブジェクトとしてまとめて管理することも可能です。

src/app/signup/page.tsx
"use client";
import { useState } from "react";

export default function SignupPage() {
  // オブジェクトで状態を管理
  const [user, setUser] = useState({
    name: "",
    email: "",
  });

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    // スプレッド構文 (...) で既存の値をコピーしつつ、新しい値で上書き
    setUser({ ...user, [e.target.name]: e.target.value });
  };

  return (
    <form className="p-8 space-y-4">
      <div>
        <label>名前</label>
        <input
          name="name"
          value={user.name}
          onChange={handleChange}
          className="border p-2 block w-full"
        />
      </div>
      <div>
        <label>メール</label>
        <input
          name="email"
          value={user.email}
          onChange={handleChange}
          className="border p-2 block w-full"
        />
      </div>
      <p>入力内容: {user.name} / {user.email}</p>
    </form>
  );
}

複雑な状態管理 (useReducer)

状態の更新ロジックが複雑になってきた場合(例:前の状態に依存する、複数の値が連動して変わる)、useState よりも useReducer が適しています。

src/app/counter-reducer/page.tsx
"use client";
import { useReducer } from "react";

// 状態の型
type State = { count: number };
// アクションの型(何をするか)
type Action = { type: 'increment' } | { type: 'decrement' } | { type: 'reset' };

// Reducer関数: (今の状態, アクション) => 新しい状態
const reducer = (state: State, action: Action): State => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return { count: 0 };
    default:
      return state;
  }
};

export default function ReducerPage() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <main className="p-8">
      <h1>Count: {state.count}</h1>
      <div className="space-x-2">
        {/* dispatchでアクションを送る */}
        <button onClick={() => dispatch({ type: 'decrement' })} className="border p-2">-</button>
        <button onClick={() => dispatch({ type: 'increment' })} className="border p-2">+</button>
        <button onClick={() => dispatch({ type: 'reset' })} className="border p-2">Reset</button>
      </div>
    </main>
  );
}

状態の受け渡し (Props)

親コンポーネントで管理している State を、子コンポーネントで使いたい場合は Props (プロップス) で渡します。

バケツリレー (Props Drilling)

sample.tsx
// ParentComponent
export default function Parent() {
  const [count, setCount] = useState(0);
  
  // 子に count と setCount を渡す
  return <Child count={count} onClick={() => setCount(count + 1)} />;
}

// ChildComponent
function Child({ count, onClick }: { count: number, onClick: () => void }) {
  // さらに孫へ...
  return <GrandChild count={count} onClick={onClick} />;
}

親から子、子から孫へと State を渡していくことを「バケツリレー」と呼びます。
階層が浅ければ問題ありませんが、深くなると管理が大変になります(Props Drilling 問題)。これを解決するのが、次章で解説する ContextGlobal State です。

まとめ(基本編)

  • useState : 基本的な状態管理。まずはこれを使う。
  • useReducer : 複雑な更新ロジックが必要な場合に使う。
  • Props : 親から子へ State を渡す基本的な手段。

基本的には useState と Props だけで多くのことができます。しかし、アプリが大規模になるとそれだけでは管理しきれなくなります。
次章の「応用編」では、より高度な状態管理について解説します。

更新履歴

  • 2025-01-04 : ページのレイアウト崩れ・誤字を修正しました。