概要
createContext + useContext を使うことで props drilling を避けつつ、
軽量なグローバル状態を実現できる。テーマ切り替えや認証状態など、更新頻度が低い値に適している。
実装
1. Context と Provider を定義する
// src/contexts/ThemeContext.tsx
import { createContext, useContext, useState } from "react";
type Theme = "light" | "dark";
type ThemeContextValue = {
theme: Theme;
toggleTheme: () => void;
};
const ThemeContext = createContext<ThemeContextValue | null>(null);
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<Theme>("light");
const toggleTheme = () => {
setTheme((prev) => (prev === "light" ? "dark" : "light"));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
/** Context の値を取得するカスタムフック */
export function useTheme(): ThemeContextValue {
const ctx = useContext(ThemeContext);
if (!ctx) {
throw new Error("useTheme は ThemeProvider の内側で使用してください");
}
return ctx;
}
2. Provider でアプリをラップする
// src/app/layout.tsx
import { ThemeProvider } from "@/contexts/ThemeContext";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ja">
<body>
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
);
}
3. 任意の子コンポーネントで使う
// src/components/ThemeToggle.tsx
"use client";
import { useTheme } from "@/contexts/ThemeContext";
export function ThemeToggle() {
const { theme, toggleTheme } = useTheme();
return (
<button
onClick={toggleTheme}
className="rounded border px-3 py-1 text-sm"
>
現在: {theme === "light" ? "ライト" : "ダーク"} → 切り替え
</button>
);
}
ポイント
- Context の初期値を
nullにして、Provider 外での使用をエラーで検出する - カスタムフック(
useTheme)でアクセスを統一する - Next.js App Router で使う場合、Provider コンポーネントは
"use client"が必要 - 頻繁に変わる状態(例: 入力中の値)には適さない。その場合は Zustand を使う