概要

useContext は、コンポーネントツリーを通じてデータを受け渡す仕組みである Context API から値を取得するためのHookである。

Reactアプリケーションでは、親から子へとデータをPropsで渡していく構造が基本となる。
しかし、コンポーネントの階層が深くなると、途中のコンポーネントがそのデータを使用しないにもかかわらず、下位のコンポーネントに渡すためだけにPropsを受け取り続ける Prop Drilling という問題が発生する。
useContext はこの問題を解消し、任意の階層からContextの値に直接アクセスできるようにする。

Context APIは、以下に示す3つの要素で構成される。

Context APIの構成要素
要素 説明
createContext コンテキストオブジェクトを作成する。
デフォルト値を引数に取る。
Provider 子コンポーネントに値を提供する。
値を変更すると、全ての購読コンポーネントが再レンダリングされる。
useContext Providerが提供する値をコンポーネントから取得する。
最も近い上位のProviderの値を返す。


React 19では、<MyContext.Provider value={...}> という構文に加えて、<MyContext value={...}> と記述する Context as Provider パターンが導入された。
また、条件分岐やループ内でも呼び出せる use() Hookが追加され、より柔軟なContext利用が可能になった。


Context APIの基本

Context APIを構成する3つの要素の使い方を以下に示す。

createContext

createContext 関数を使用してコンテキストオブジェクトを作成する。
引数にはProviderが存在しない場合に返されるデフォルト値を指定する。

 import { createContext } from 'react';
 
 // デフォルト値を指定してコンテキストを作成する
 const ThemeContext = createContext('light');
 
 // デフォルト値にオブジェクトを指定することもできる
 const UserContext = createContext({ name: '未ログイン', isLoggedIn: false });


デフォルト値は、コンポーネントツリーのどこにも対応するProviderが存在しない場合にのみ使用される。

TypeScriptでの型安全なパターンについては、後述の#TypeScriptでの型定義セクションを参照すること。

Provider

Providerは、子孫コンポーネントにContextの値を提供するコンポーネントである。
Providerで囲まれたコンポーネントは、useContext を通じて値にアクセスできる。

React 18以前では、必ず MyContext.Provider という形式で記述する必要があった。

  • React 18以前のProvider構文
     function App() {
        return (
           <ThemeContext.Provider value="dark">
              <Header />
              <Main />
           </ThemeContext.Provider>
        );
     }
    

  • React 19以降のProvider構文 (Context as Provider)
    React 19以降では、コンテキストオブジェクト自体をProviderとして直接使用できる。


 function App() {
    return (
       <ThemeContext value="dark">
          <Header />
          <Main />
       </ThemeContext>
    );
 }


useContext

useContext Hookは、最も近い上位のProviderが提供する値を返す。

 import { useContext } from 'react';
 
 function Header() {
    // 最も近いThemeContext.Providerのvalueを取得する
    const theme = useContext(ThemeContext);
 
    return (
       <header className={theme === 'dark' ? 'header-dark' : 'header-light'}>
          サイトヘッダ
       </header>
    );
 }


Providerがネストしている場合、useContext は呼び出し元のコンポーネントから最も近い (最も内側の) Providerの値を返す。


TypeScriptでの型定義

TypeScriptを使用する場合、Contextの型を明示的に定義することで型安全なアクセスが可能になる。

Contextの型定義

デフォルト値に undefined を使用するパターンが推奨される。
このパターンにより、Providerの外でContextを使用した場合にTypeScriptが警告を出すようになる。

 import { createContext, useState } from 'react';
 
 interface ThemeContextValue {
    theme: 'light' | 'dark';
    setTheme: (theme: 'light' | 'dark') => void;
 }
 
 // デフォルト値をundefinedにして型パラメータを指定する
 const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);

 function ThemeProvider({ children }: { children: React.ReactNode }) {
    const [theme, setTheme] = useState<'light' | 'dark'>('light');
 
    return (
       <ThemeContext.Provider value={{ theme, setTheme }}>
          {children}
       </ThemeContext.Provider>
    );
 }


カスタムHookによる型安全なアクセス

createContext<T | undefined>(undefined) パターンと組み合わせて、カスタムHookを作成することでより安全にContextを利用できる。

カスタムHookの中でundefinedチェックを行うことにより、Provider未配置時のエラーを早期に検出できる。

 import { useContext } from 'react';
 
 // ThemeContext用のカスタムHook
 function useTheme(): ThemeContextValue {
    const context = useContext(ThemeContext);
    if (context === undefined) {
       throw new Error('useTheme は ThemeProvider の内側で使用してください');
    }
    return context;
 }
 
 // 使用側のコンポーネント
 function ThemeToggleButton() {
    const { theme, setTheme } = useTheme();
 
    return (
       <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
          現在のテーマ: {theme}
       </button>
    );
 }


このパターンを使用することにより、以下に示すメリットが得られる。

  • 型安全性
    戻り値が常に ThemeContextValue 型であることが保証される。
  • エラーの早期検出
    Provider未配置の場合に明確なエラーメッセージが表示される。
  • 利便性
    使用側で毎回 useContext とContextオブジェクトをimportする必要がなくなる。



React 19でのContextの変更

React 19では、Contextに関する構文と機能が拡張された。

Context as Providerパターン

React 19以降では、コンテキストオブジェクトをProviderとして直接使用できる。
これにより、MyContext.Provider という冗長な記述が不要になる。

 import { createContext, useState } from 'react';
 
 const ThemeContext = createContext<'light' | 'dark'>('light');
 
 function App() {
    const [theme, setTheme] = useState<'light' | 'dark'>('light');
 
    return (
       // React 19: <ThemeContext.Provider> の代わりに <ThemeContext> を直接使用できる
       <ThemeContext value={theme}>
          <Header />
          <Main />
       </ThemeContext>
    );
 }


use() Hook

React 19では、useContext の改良版として use() Hookが追加された。
use() の最大の特徴は、条件分岐やループの内側でも呼び出せる点である。

 import { use } from 'react';
 
 function ThemeDisplay({ showTheme }: { showTheme: boolean }) {
    // use()は、if文の内側でも呼び出せる (通常のHookはこれができない)
    if (showTheme) {
       const theme = use(ThemeContext);
       return <p>現在のテーマ: {theme}</p>;
    }
 
    return <p>テーマ非表示</p>;
 }


下表に、useContextuse() の比較を示す。

useContext と use() の比較
項目 useContext use()
React バージョン React 16.8以降 React 19以降
条件分岐内での呼び出し 不可 可能
ループ内での呼び出し 不可 可能
Promiseの受け取り 不可 可能 (Suspenseと組み合わせて使用)
使用場所 関数コンポーネントのトップレベルのみ より柔軟な場所で使用可能



サンプルコード

実務での使用頻度が高いContextのパターンを以下に示す。

テーマ切替

ライト/ダークモードのようなテーマ設定を、アプリケーション全体で共有するパターンである。

 import { createContext, useContext, useState } from 'react';
 
 type Theme = 'light' | 'dark';
 
 interface ThemeContextValue {
    theme: Theme;
    resolvedTheme: Theme;
    setTheme: (theme: Theme) => void;
    toggleTheme: () => void;
 }
 
 const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
 
 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, resolvedTheme: theme, setTheme, toggleTheme }}>
          {children}
       </ThemeContext.Provider>
    );
 }
 
 export function useTheme(): ThemeContextValue {
    const context = useContext(ThemeContext);
    if (!context) throw new Error('useTheme は ThemeProvider の内側で使用してください');
    return context;
 }
 
 // 使用例
 function ThemeToggleButton() {
    const { theme, toggleTheme } = useTheme();
    return (
       <button onClick={toggleTheme}>
          {theme === 'light' ? 'ダークモードに切替' : 'ライトモードに切替'}
       </button>
    );
 }


認証情報管理

ログインユーザの情報をアプリケーション全体で共有するパターンである。

 import { createContext, useContext, useState } from 'react';
 
 interface User {
    id: string;
    name: string;
    email: string;
 }
 
 interface AuthContextValue {
    user: User | null;
    isLoading: boolean;
    login: (email: string, password: string) => Promise<void>;
    logout: () => void;
 }
 
 const AuthContext = createContext<AuthContextValue | undefined>(undefined);
 
 export function AuthProvider({ children }: { children: React.ReactNode }) {
    const [user, setUser] = useState<User | null>(null);
    const [isLoading, setIsLoading] = useState(false);
 
    const login = async (email: string, password: string) => {
       setIsLoading(true);
       try {
          // 認証APIを呼び出す処理
          const userData = await fetchUser(email, password);
          setUser(userData);
       }
       finally {
          setIsLoading(false);
       }
    };
 
    const logout = () => {
       setUser(null);
    };
 
    return (
       <AuthContext.Provider value={{ user, isLoading, login, logout }}>
          {children}
       </AuthContext.Provider>
    );
 }
 
 export function useAuth(): AuthContextValue {
    const context = useContext(AuthContext);
    if (!context) throw new Error('useAuth は AuthProvider の内側で使用してください');
    return context;
 }


言語設定 (i18n)

アプリケーションの表示言語をグローバルに管理するパターンである。

 import { createContext, useContext, useState, useCallback } from 'react';
 
 type Locale = 'ja' | 'en' | 'zh';
 
 type TranslationKey = 'greeting' | 'farewell' | 'submit';
 
 const translations: Record<Locale, Record<TranslationKey, string>> = {
    ja: { greeting: 'こんにちは', farewell: 'さようなら', submit: '送信' },
    en: { greeting: 'Hello', farewell: 'Goodbye', submit: 'Submit' },
    zh: { greeting: '你好', farewell: '再见', submit: '提交' },
 };
 
 interface I18nContextValue {
    locale: Locale;
    setLocale: (locale: Locale) => void;
    t: (key: TranslationKey) => string;
 }
 
 const I18nContext = createContext<I18nContextValue | undefined>(undefined);
 
 export function I18nProvider({ children }: { children: React.ReactNode }) {
    const [locale, setLocale] = useState<Locale>('ja');
 
    const t = useCallback((key: TranslationKey): string => {
       return translations[locale][key];
    }, [locale]);
 
    return (
       <I18nContext.Provider value={{ locale, setLocale, t }}>
          {children}
       </I18nContext.Provider>
    );
 }
 
 export function useI18n(): I18nContextValue {
    const context = useContext(I18nContext);
    if (!context) throw new Error('useI18n は I18nProvider の内側で使用してください');
    return context;
 }



Providerのネスト

複数のContextを組み合わせる場合は、Providerをネストして使用する。

アプリケーションが成長すると、多数のProviderがネストされて記述が複雑になる問題が生じる。
これを解消するためのパターンとして、CombinedProviderパターンとComposeProvidersパターンがある。

 // 複数のProviderを1つにまとめるCombinedProviderパターン
 function CombinedProvider({ children }: { children: React.ReactNode }) {
    return (
       <ThemeProvider>
          <AuthProvider>
             <I18nProvider>
                {children}
             </I18nProvider>
          </AuthProvider>
       </ThemeProvider>
    );
 }
 
 // App.tsxでの使用
 function App() {
    return (
       <CombinedProvider>
          <Router>
             <AppContent />
          </Router>
       </CombinedProvider>
    );
 }


さらに汎用的なアプローチとして、Providerの配列を動的に合成するComposeProvidersパターンがある。

 type ProviderComponent = ({ children }: { children: React.ReactNode }) => JSX.Element;
 
 function composeProviders(...providers: ProviderComponent[]) {
    return ({ children }: { children: React.ReactNode }) => {
       return providers.reduceRight(
          (acc, Provider) => <Provider>{acc}</Provider>,
          children as JSX.Element
       );
    };
 }
 
 // 使用例
 const AppProviders = composeProviders(ThemeProvider, AuthProvider, I18nProvider);
 
 function App() {
    return (
       <AppProviders>
          <AppContent />
       </AppProviders>
    );
 }



パフォーマンスの考慮事項

Contextを使用する場合は、パフォーマンスへの影響を考慮する必要がある。

Context値変更時の再レンダリング

Contextの値が変更されると、そのContextを購読している全てのコンポーネントが再レンダリングされる。
オブジェクトや配列をContext値として渡す場合、参照が毎回変わるため不必要な再レンダリングが発生しやすい。

 // 問題のあるパターン : レンダリングのたびに新しいオブジェクトが作成される
 function ProblematicProvider({ children }: { children: React.ReactNode }) {
    const [user, setUser] = useState<User | null>(null);
 
    return (
       // value に毎回新しいオブジェクトが渡されるため、不要な再レンダリングが起きる
       <UserContext.Provider value={{ user, setUser }}>
          {children}
       </UserContext.Provider>
    );
 }


useMemoによる最適化

useMemo を使用してContext値をメモ化することにより、不必要な再レンダリングを防ぐことができる。

 import { createContext, useContext, useState, useMemo, useCallback } from 'react';
 
 function OptimizedProvider({ children }: { children: React.ReactNode }) {
    const [user, setUser] = useState<User | null>(null);
 
    // useCallback で関数参照を安定化する
    const login = useCallback(async (email: string, password: string) => {
       const userData = await fetchUser(email, password);
       setUser(userData);
    }, []);
 
    const logout = useCallback(() => {
       setUser(null);
    }, []);
 
    // useMemo でContext値をメモ化する
    const value = useMemo(
       () => ({ user, login, logout }),
       [user, login, logout]
    );
 
    return (
       <UserContext.Provider value={value}>
          {children}
       </UserContext.Provider>
    );
 }


Context分割パターン

1つの大きなContextに多くの値を格納すると、一部の値が変わるだけでそのContextを使用する全てのコンポーネントが再レンダリングされる。
更新頻度の異なるデータを別々のContextに分割することにより、再レンダリングの範囲を最小化できる。

 // 分割前 : 1つの大きなContext
 // theme が変わるだけで user を使うコンポーネントも再レンダリングされる
 const AppContext = createContext<{ theme: Theme; user: User | null } | undefined>(undefined);
 
 // 分割後 : 役割ごとに独立したContext
 const ThemeContext = createContext<Theme | undefined>(undefined);
 const UserContext = createContext<User | null | undefined>(undefined);
 
 // StateとDispatchを分離するパターン (useReducerとの組み合わせで特に有効)
 const StateContext = createContext<AppState | undefined>(undefined);
 const DispatchContext = createContext<React.Dispatch<AppAction> | undefined>(undefined);



useContextとuseReducerの組み合わせ

useContextuseReducer を組み合わせることにより、ReduxのようなグローバルなFlux状態管理パターンを実現できる。

StateContextとDispatchContextを分離することで、状態を読み取るだけのコンポーネントはアクションを発行しても再レンダリングされず、パフォーマンスが向上する。

 import { createContext, useContext, useReducer, useMemo } from 'react';
 
 interface AppState {
    count: number;
    user: User | null;
 }
 
 type AppAction =
    | { type: 'INCREMENT' }
    | { type: 'DECREMENT' }
    | { type: 'SET_USER'; payload: User | null };
 
 function appReducer(state: AppState, action: AppAction): AppState {
    switch (action.type) {
       case 'INCREMENT':
          return { ...state, count: state.count + 1 };
       case 'DECREMENT':
          return { ...state, count: state.count - 1 };
       case 'SET_USER':
          return { ...state, user: action.payload };
       default:
          return state;
    }
 }
 
 // StateとDispatchを別々のContextに分離する
 const StateContext = createContext<AppState | undefined>(undefined);
 const DispatchContext = createContext<React.Dispatch<AppAction> | undefined>(undefined);
 
 const initialState: AppState = { count: 0, user: null };
 
 export function AppStateProvider({ children }: { children: React.ReactNode }) {
    const [state, dispatch] = useReducer(appReducer, initialState);
 
    return (
       <DispatchContext.Provider value={dispatch}>
          <StateContext.Provider value={state}>
             {children}
          </StateContext.Provider>
       </DispatchContext.Provider>
    );
 }
 
 // 状態を読み取るカスタムHook
 export function useAppState(): AppState {
    const context = useContext(StateContext);
    if (!context) throw new Error('useAppState は AppStateProvider の内側で使用してください');
    return context;
 }
 
 // アクションを発行するカスタムHook
 export function useAppDispatch(): React.Dispatch<AppAction> {
    const context = useContext(DispatchContext);
    if (!context) throw new Error('useAppDispatch は AppStateProvider の内側で使用してください');
    return context;
 }



よくある間違い

useContext を使用する時によく発生する誤りと対処法を以下に示す。

Provider未配置

useContext を呼び出したコンポーネントが、対応するProviderの外側に配置されている場合、createContext に渡したデフォルト値が返される。
デフォルト値が undefined の場合、プロパティアクセスでエラーが発生する。

 // 誤り : ThemeProviderの外側でuseThemeを呼び出している
 function App() {
    return (
       <div>
          // ThemeToggleButtonは、ThemeProviderの外側にある
          <ThemeToggleButton />
          <ThemeProvider>
             <Main />
          </ThemeProvider>
       </div>
    );
 }
 
 // 正しい : ThemeProviderの内側に配置する
 function App() {
    return (
       <ThemeProvider>
          <ThemeToggleButton />
          <Main />
       </ThemeProvider>
    );
 }


また、コンポーネント関数の内側でProviderを返しても、そのコンポーネント自体はProviderの外側にあるため、自分自身はContextの値を受け取ることができない。

 // 誤り : useTheme() は ThemeProvider より上の階層で呼び出されている
 function ThemeAwareProvider({ children }: { children: React.ReactNode }) {
    const { theme } = useTheme(); // この時点ではまだProviderが存在しない
 
    return (
       <ThemeProvider>
          <div className={theme}>
             {children}
          </div>
       </ThemeProvider>
    );
 }


不必要なContext使用

Contextは、アプリケーション全体 または 複数の深い階層に渡って、データを共有する場合に適したツールである。
1〜2階層のデータ受け渡しであれば、Propsを使用する方がシンプルでデータの流れが明確になる。

 // 不必要なContextの例 : 1階層だけならPropsで十分
 // Context使用 (過剰)
 function Parent() {
    return <UserNameContext.Provider value="田中"><Child /></UserNameContext.Provider>;
 }
 
 function Child() {
    const name = useContext(UserNameContext);
    return <p>{name}</p>;
 }
 
 // Propsを使用 (適切)
 function Parent() {
    return <Child name="田中" />;
 }
 
 function Child({ name }: { name: string }) {
    return <p>{name}</p>;
 }


下表に、PropsとContextの使い分けの目安を示す。

Props と Contextの使い分け
状況 推奨手段
1〜2階層のデータ受け渡し Props
多くのコンポーネントで必要なデータ Context
コンポーネントツリーの深い階層へのデータ伝達 Context
テーマ、認証、言語設定などのグローバルな状態 Context
局所的なコンポーネント状態 useState / useReducer (Propsで渡す)



関連情報