useContext

useContext で深い子孫へ値を渡す仕組みと、Context を使う判断を学ぶ。

useContext は、遠い親が提供した値を props の中継なしで読む Hook です。テーマ、現在のユーザー、画面の表示密度のように、多くの子孫が同じ値を必要とするときに使います。

props の中継を減らす

値を使わない中間コンポーネントまで props を渡し続けると、構造の変更に弱くなります。Context は共有したい値の通り道を作ります。ただし、近い親子のデータ受け渡しまで Context にする必要はありません。

import { createContext, useContext } from 'react';

const ThemeContext = createContext<'light' | 'dark'>('light');

function SaveButton() {
  const theme = useContext(ThemeContext);
  return <button className={theme}>保存</button>;
}

export function Settings() {
  return (
    <ThemeContext value="dark">
      <SaveButton />
    </ThemeContext>
  );
}

useContext(ThemeContext) は最も近い Provider の value を読み、値が変わればその Context を読むコンポーネントを再レンダーします。React 19 より前の書き方では <ThemeContext.Provider value="dark"> を使います。

値は Provider から下へ流れます。途中のコンポーネントが props を受け取らなくても、子孫は最寄りの Provider を探せます。同じ Context の Provider を入れ子にした場合は、近い方の値が優先されます。親が自分の Provider を返していても、その親自身の useContext は外側の Provider を読みます。

state と組み合わせる

Context は値を保存する機能ではありません。共有する値が変化するなら、Provider 側で useState や useReducer を使います。読み取り用の値と更新関数を必要な範囲にだけ公開します。

const AuthContext = createContext<{ signedIn: boolean; signOut: () => void } | null>(null);

function Profile() {
  const auth = useContext(AuthContext);
  if (auth === null) throw new Error('AuthContext が必要です');
  return <button onClick={auth.signOut}>{auth.signedIn ? 'ログアウト' : 'ログイン'}</button>;
}

Provider を読む側で毎回 null 判定を書く代わりに、専用 Hook に閉じ込める方法もあります。

function useAuth() {
  const auth = useContext(AuthContext);
  if (auth === null) throw new Error('AuthProvider の内側で使ってください');
  return auth;
}

この形なら利用側は const { signedIn, signOut } = useAuth() と書けます。Provider を置き忘れたときも、デフォルト値で静かに動き続けるのではなく、開発中に原因を特定できます。

使う判断と落とし穴

アプリ全体の設定や、離れた複数箇所で読む安定した共有値には向いています。一方、頻繁に変わる大きなオブジェクトを1つの Context に入れると、値を読む広い範囲が更新されます。Context を目的別に分け、まずは props・コンポーネント合成で表現できないかを検討します。

Provider がない場合は createContext のデフォルト値が返ります。開発時の不備を検出したい必須 Context には null を初期値にして、専用 Hook でエラーにする設計が安全です。

Context の値にオブジェクトや関数を渡す場合は、その参照が変わるたびに購読者へ更新が届きます。問題が実測できたときだけ useMemo と useCallback で Provider の値を安定させます。更新頻度が異なる値を同じ Context に詰め込まないことが、まず効く最適化です。

Context は「グローバル変数」ではない

Context はコンポーネントツリーの特定の範囲に値を提供します。そのため、テストでは対象を Provider で包んで値を差し替えられます。また、同じ画面で別々のテーマや言語を扱うこともできます。モジュールのトップレベルに可変値を置くグローバル変数とは、更新通知とスコープの点で異なります。

一方で、何でも Context に入れると依存関係が JSX から見えなくなります。「このボタンは theme と auth に依存する」という事実を追いにくくなるため、親子数段の受け渡しなら props の方が明快です。データを渡す代わりに children を渡すコンポーネント合成も、有力な選択肢です。

まとめ

useContext は props のバケツリレーを避けるための共有経路です。状態管理そのものではないため、共有範囲と更新頻度を小さく保ちましょう。複雑な更新規則を共有するときは次に useReducer を検討します。

Sources