発行:
更新:

状態管理(応用編:Global State & Server State)

前章では、useState や Props による基本的な状態管理を学びました。
ここでは、アプリが大規模になったり、Next.js 特有の機能を活かしたりするための「応用的な状態管理」について解説します。

1. Global State (Context API)

Props Drilling(バケツリレー)を解決するための、React 標準の機能です。
「離れたコンポーネント同士でデータを共有したい」時に使います(例:テーマ設定、ログインユーザー情報)。

使い方の流れ

  1. Context を作成する
  2. Provider で囲む
  3. useContext で値を取り出す
src/contexts/ThemeContext.tsx
"use client";
import { createContext, useContext, useState } from "react";

// 1. Contextの作成
const ThemeContext = createContext("light");

// 2. Providerコンポーネント(親)
export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState("light");
  
  return (
    <ThemeContext.Provider value={theme}>
      <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
        Toggle Theme
      </button>
      {children}
    </ThemeContext.Provider>
  );
}

// 3. 値を使うためのカスタムフック(子)
export function useTheme() {
  return useContext(ThemeContext);
}

注意点:Context の値が変わると、それを使っているコンポーネントは 全て再レンダリング されます。頻繁に更新される巨大なデータには向きません。

2. URL as State (URL状態管理)

Next.js (App Router) で最も重要な概念の一つです。 検索フォームの入力内容や、ページネーションのページ数などは、useState ではなく URL(クエリパラメータ) で管理すべきです。

なぜ URL で管理するのか?

  • シェアできる : URL をコピーして友人に送れば、同じ検索結果を見てもらえる。
  • ブラウザバックできる : 「戻る」ボタンで前の検索結果に戻れる。
  • リロードしても消えない : useState はリロードすると消えるが、URL は残る。

実装例 (useSearchParams)

src/app/search/page.tsx
"use client";
import { useSearchParams, useRouter, usePathname } from "next/navigation";

export default function SearchPage() {
  const searchParams = useSearchParams();
  const { replace } = useRouter();
  const pathname = usePathname();

  // URLから ?q=... を取得
  const query = searchParams.get("query")?.toString() || "";

  const handleSearch = (term: string) => {
    const params = new URLSearchParams(searchParams);
    if (term) {
      params.set("query", term);
    } else {
      params.delete("query");
    }
    // URLを書き換える(ページ遷移せずに)
    replace(`${pathname}?${params.toString()}`);
  };

  return (
    <div>
      <input
        defaultValue={query}
        onChange={(e) => handleSearch(e.target.value)}
        className="border p-2"
      />
      <p>検索キーワード: {query}</p>
    </div>
  );
}

3. Server State vs Client State

状態は「どこにあるデータか」で2種類に分けられます。

  1. Client State : UI の状態。モーダルの開閉、入力中の文字など。(これまで解説したもの)
  2. Server State : データベースにあるデータ。記事一覧、ユーザー情報など。

Server State は、API から取得してキャッシュしたり、更新したりする必要があります。
Next.js App Router では、 Server Components で直接 await fetch() するのが基本 ですが、Client Component で扱う場合はライブラリを使うのが一般的です。

代表的なライブラリ

  • SWR (Vercel製): シンプルで軽量。
  • TanStack Query (React Query) : 高機能。

これらを使うと、「データの取得中」「エラー」「キャッシュの更新」などを簡単に管理できます。

4. 外部ライブラリ (Zustand, Recoil, Jotai)

Context API よりも高機能な Global State 管理が必要な場合に使います。

  • Zustand (ズスタンド) : 今もっとも人気がある。シンプルで使いやすい。
  • Jotai (ジョータイ) : Atom(原子)という単位で状態を管理する。
  • Recoil (リコイル) : Facebook製だが、最近は更新が停滞気味。

いつ使うべき?

「アプリ全体で複雑なデータフローがある」「Context だと再レンダリングのパフォーマンス問題が起きる」といった場合に導入を検討してください。小規模なアプリでは不要なことが多いです。

まとめ

Next.js アプリの状態管理は、以下の優先順位で考えると綺麗に設計できます。

  1. URL : 検索条件やフィルタなど、シェアすべき情報は URL に入れる。
  2. Server State : サーバーのデータは Server Components か SWR/React Query で扱う。
  3. Local State (useState/useReducer) : 特定のコンポーネントだけで完結するUIの状態。
  4. Global State (Context/Zustand) : 上記に当てはまらず、複数の場所で共有したいデータ。

適材適所でツールを使い分けましょう!

更新履歴

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