useContext はコンポーネントでコンテクスト (Context) の読み取りとサブスクライブ(subscribe, 変更の受け取り)を行うための React フックです。

const value = useContext(SomeContext)

リファレンス

useContext(SomeContext)

コンポーネントのトップレベルで useContext を呼び出して、コンテクストを読み取り、サブスクライブします。

import { useContext } from 'react';

function MyComponent() {
const theme = useContext(ThemeContext);
// ...

さらに例を見る

引数

  • SomeContext: 事前に createContext で作成したコンテクストです。コンテクストとはそれ自体が情報を保持しているわけではなく、コンポーネントで提供 (provide) したり読み取ったりできる「情報の種別」を表すものです。

返り値

useContext は、呼び出したコンポーネントのコンテクスト値を返します。コンポーネントがツリー内で呼び出されるとき、その上位に位置する最も近い SomeContext.Provider に渡された value として決定されます。そのようなプロバイダが存在しない場合は、返り値はそのコンテクストの createContext に渡した defaultValue になります。その返り値は常に最新になります。React は、コンテクストを読み取ったコンポーネントが変更されると、自動的に再レンダーします。

注意点

  • コンポーネントの useContext() 呼び出しは、同じコンポーネントから返されるプロバイダの影響を受けません。対応する <Context.Provider> は、useContext() を呼び出すコンポーネントのにある必要があります。
  • 特定のコンテクストを使用する全ての子コンポーネントは、異なる value を受け取るプロバイダから始まり、React によって自動的に再レンダーします。前の値と次の値は、Object.is で比較されます。memo で再レンダーをスキップしても、子のプロバイダは新しいコンテクスト値を受け取ることはありません。
  • ビルドシステムが生成する出力の中にモジュールの重複がある場合(シンボリックリンクで起こり得る場合がある)、コンテクストが壊れる可能性があります。コンテクストを介した値の受け渡しが動作するのは、コンテクストを提供するために使用する SomeContext と、読み込むために使用する SomeContext が、=== による比較で厳密に同じオブジェクトである場合のみです。

使い方

ツリーの深くにデータを渡す

コンポーネントのトップレベルで useContext を呼び出してコンテクストを読み取り、サブスクライブします。

import { useContext } from 'react';

function Button() {
const theme = useContext(ThemeContext);
// ...

useContext渡したコンテクストに対応するコンテクストの値を返します。コンテクストの値を決定するために、React はコンポーネントツリーを探索し、そのコンテクストに対して最も近い上位のコンテクストプロバイダを見つけます。

コンテクストを上記の Button に渡すには、該当のボタンあるいはその親コンポーネントのいずれかを、対応するコンテクストプロバイダでラップします。

function MyPage() {
return (
<ThemeContext.Provider value="dark">
<Form />
</ThemeContext.Provider>
);
}

function Form() {
// ... renders buttons inside ...
}

プロバイダと Button の間にどれだけ多くのコンポーネントが挟まっていても関係ありません。Form の内部のどこかButtonuseContext(ThemeContext) を呼び出すとき、値として "dark" を受け取ります。

落とし穴

useContext() は常に、呼び出すコンポーネントより最も近い、上位にあるプロバイダを探します。上方向に探索を行うので、useContext() を呼び出すコンポーネント自体にあるプロバイダは考慮しません

import { createContext, useContext } from 'react';

const ThemeContext = createContext(null);

export default function MyApp() {
  return (
    <ThemeContext.Provider value="dark">
      <Form />
    </ThemeContext.Provider>
  )
}

function Form() {
  return (
    <Panel title="Welcome">
      <Button>Sign up</Button>
      <Button>Log in</Button>
    </Panel>
  );
}

function Panel({ title, children }) {
  const theme = useContext(ThemeContext);
  const className = 'panel-' + theme;
  return (
    <section className={className}>
      <h1>{title}</h1>
      {children}
    </section>
  )
}

function Button({ children }) {
  const theme = useContext(ThemeContext);
  const className = 'button-' + theme;
  return (
    <button className={className}>
      {children}
    </button>
  );
}


コンテクストを介したデータの更新

多くの場合、時間とともにコンテクストを変化させたいと思うでしょう。コンテクストを更新するには、それを state と組み合わせます。親コンポーネントで state 変数を宣言し、現在の state をコンテクストの値としてプロバイダに渡します。

function MyPage() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value={theme}>
<Form />
<Button onClick={() => {
setTheme('light');
}}>
Switch to light theme
</Button>
</ThemeContext.Provider>
);
}

これにより、プロバイダの内部にある、どの Button も現在の theme の値を受け取るようになります。setTheme を呼び出してプロバイダに渡す theme 値を更新すると、すべての Button コンポーネントは新たな値である 'light' を使って再レンダーされます。

Examples of updating context

1/5:
コンテクストを介して値を更新する

この例では、MyApp コンポーネントが state 変数を保持し、それが ThemeContext プロバイダに渡されます。「ダークモード」のチェックボックスを選択すると、ステートが更新されます。提供された値を変更すると、そのコンテクストを使用しているすべてのコンポーネントが再レンダーされます。

import { createContext, useContext, useState } from 'react';

const ThemeContext = createContext(null);

export default function MyApp() {
  const [theme, setTheme] = useState('light');
  return (
    <ThemeContext.Provider value={theme}>
      <Form />
      <label>
        <input
          type="checkbox"
          checked={theme === 'dark'}
          onChange={(e) => {
            setTheme(e.target.checked ? 'dark' : 'light')
          }}
        />
        Use dark mode
      </label>
    </ThemeContext.Provider>
  )
}

function Form({ children }) {
  return (
    <Panel title="Welcome">
      <Button>Sign up</Button>
      <Button>Log in</Button>
    </Panel>
  );
}

function Panel({ title, children }) {
  const theme = useContext(ThemeContext);
  const className = 'panel-' + theme;
  return (
    <section className={className}>
      <h1>{title}</h1>
      {children}
    </section>
  )
}

function Button({ children }) {
  const theme = useContext(ThemeContext);
  const className = 'button-' + theme;
  return (
    <button className={className}>
      {children}
    </button>
  );
}

value="dark""dark" という文字列を渡しますが、value={theme} は JavaScript の theme 変数の値を JSX の中括弧 で渡しすことに注意してください。中括弧を使うことで、文字列以外のコンテクスト値も渡すことができます。


フォールバックとなるデフォルト値の指定

React があるコンテクストに対応するプロバイダを親ツリーで見つけられない場合、useContext() が返すコンテクストの値は、コンテクストを作成したときに指定したデフォルト値と等しくなります:

const ThemeContext = createContext(null);

初期値は絶対に変更されません。コンテクストを更新したいなら、上記で説明したように、state と一緒に使用します。

多くの場合、null の代わりに初期値として意味のある値を使います。例えば :

const ThemeContext = createContext('light');

こうすることで、該当のプロバイダーがないコンポーネントを間違ってレンダーしてしまっても、壊れることはありません。テスト環境で多くのプロバイダを設定しなくても、コンポーネントがうまく動作するようになります。

下記の例では、「テーマの切り替え」ボタンは常に light な色調になります。それはどのテーマコンテクストプロバイダの外部にあるためであり、初期値としてのコンテクストテーマ値は 'light' だからです。テーマの初期値を 'dark' に変更してみてください。

import { createContext, useContext, useState } from 'react';

const ThemeContext = createContext('light');

export default function MyApp() {
  const [theme, setTheme] = useState('light');
  return (
    <>
      <ThemeContext.Provider value={theme}>
        <Form />
      </ThemeContext.Provider>
      <Button onClick={() => {
        setTheme(theme === 'dark' ? 'light' : 'dark');
      }}>
        Toggle theme
      </Button>
    </>
  )
}

function Form({ children }) {
  return (
    <Panel title="Welcome">
      <Button>Sign up</Button>
      <Button>Log in</Button>
    </Panel>
  );
}

function Panel({ title, children }) {
  const theme = useContext(ThemeContext);
  const className = 'panel-' + theme;
  return (
    <section className={className}>
      <h1>{title}</h1>
      {children}
    </section>
  )
}

function Button({ children, onClick }) {
  const theme = useContext(ThemeContext);
  const className = 'button-' + theme;
  return (
    <button className={className} onClick={onClick}>
      {children}
    </button>
  );
}


ツリーにある一部のコンテクストを上書きする

ツリーにある異なる値を持つプロバイダでラップすることにより、一部のコンテクストを上書きできます。

<ThemeContext.Provider value="dark">
...
<ThemeContext.Provider value="light">
<Footer />
</ThemeContext.Provider>
...
</ThemeContext.Provider>

必要な回数だけ、プロバイダをネストして上書きすることができます。

例を試す

1/2:
テーマの上書き

この例では、Footer内部にあるボタンは、外部にあるボタン("dark")とは違うコンテクスト値("light")を受け取ります。

import { createContext, useContext } from 'react';

const ThemeContext = createContext(null);

export default function MyApp() {
  return (
    <ThemeContext.Provider value="dark">
      <Form />
    </ThemeContext.Provider>
  )
}

function Form() {
  return (
    <Panel title="Welcome">
      <Button>Sign up</Button>
      <Button>Log in</Button>
      <ThemeContext.Provider value="light">
        <Footer />
      </ThemeContext.Provider>
    </Panel>
  );
}

function Footer() {
  return (
    <footer>
      <Button>Settings</Button>
    </footer>
  );
}

function Panel({ title, children }) {
  const theme = useContext(ThemeContext);
  const className = 'panel-' + theme;
  return (
    <section className={className}>
      {title && <h1>{title}</h1>}
      {children}
    </section>
  )
}

function Button({ children }) {
  const theme = useContext(ThemeContext);
  const className = 'button-' + theme;
  return (
    <button className={className}>
      {children}
    </button>
  );
}


オブジェクトや関数を渡すときの再レンダーの最適化

コンテクストを介して、オブジェクトや関数を含んだどんな値も渡すことができます。

function MyApp() {
const [currentUser, setCurrentUser] = useState(null);

function login(response) {
storeCredentials(response.credentials);
setCurrentUser(response.user);
}

return (
<AuthContext.Provider value={{ currentUser, login }}>
<Page />
</AuthContext.Provider>
);
}

ここでは、context value は、2 つのプロパティを持つ JavaScript のオブジェクトで、そのうちの 1 つは関数になります。MyApp が再レンダーされる度に(例えば、ルート更新など)、これは異なるオブジェクトを指し、異なる関数を指すため、React はツリーにある useContext(AuthContext) を呼び出す、すべてのコンポーネントを再レンダーしなければなりません。

小規模なアプリでは、問題になりません。ですが、currentUser のような基礎となるデータが変更されていないなら、再レンダーする必要はありません。React がその事実を最大限に活用できるように、login 関数を useCallback でラップし、オブジェクトの生成を useMemo にラップすることができます。これはパフォーマンスの最適化です:

import { useCallback, useMemo } from 'react';

function MyApp() {
const [currentUser, setCurrentUser] = useState(null);

const login = useCallback((response) => {
storeCredentials(response.credentials);
setCurrentUser(response.user);
}, []);

const contextValue = useMemo(() => ({
currentUser,
login
}), [currentUser, login]);

return (
<AuthContext.Provider value={contextValue}>
<Page />
</AuthContext.Provider>
);
}

この変更の結果、MyApp が再レンダーする必要があっても、currentUser が変更されていない限り、useContext(AuthContext) を呼び出すコンポーネントを再レンダーする必要はありません。

詳しくは useMemouseCallback について、読んでください。


トラブルシューティング

MyComponent はプロバイダからの値を見れません

これが起こる一般的な方法はいくつかあります:

  1. useContext() を呼び出すコンポーネントと同じ箇所(または、下位の箇所)で <SomeContext.Provider> をレンダーします。<SomeContext.Provider>useContext() を呼び出すコンポーネントの上位外部に移動してください。
  2. コンポーネントを <SomeContext.Provider> でラップし忘れているかもしれませんし、思っていたよりもツリー内の違うの箇所に配置してしまったかもしれません。React DevTools. を使って階層が正しいか確認してみてください。
  3. プロバイダーコンポーネントから見た SomeContext と、利用側のコンポーネントから見た SomeContext が、ビルドツールの問題により 2 つの異なるオブジェクトになっているかもしれません。例えば、シンボリックリンクを使用している場合などに発生します。これを確認するために、それらを window.SomeContext1window.SomeContext2 のようなグローバル変数に割り当て、コンソールで window.SomeContext1 === window.SomeContext2 が成り立つか確認してみてください。もし同一でないなら、ビルドツールレベルで、その問題を修正する必要があります。

初期値は違うのに、コンテクストからは常に undefined が返ってくる

ツリーの中に value なしのプロバイダがあるかもしれません:

// 🚩 Doesn't work: no value prop
<ThemeContext.Provider>
<Button />
</ThemeContext.Provider>

value を指定し忘れた場合、それは value={undefined} を渡すのと同じです。

また、誤って props として違う名前を使っているのかもしれません:

// 🚩 Doesn't work: prop should be called "value"
<ThemeContext.Provider theme={theme}>
<Button />
</ThemeContext.Provider>

どちらの場合も、React からの警告がコンソールに表示されるはずです。修正するには、props として value を使います:

// ✅ Passing the value prop
<ThemeContext.Provider value={theme}>
<Button />
</ThemeContext.Provider>

createContext(defaultValue) で指定するデフォルト値は、ツリーの上側に一致するプロバイダが一切存在しない場合にのみ使用されることに注意してください。親のツリーのどこかに <SomeContext.Provider value={undefined}> のようなコンポーネントがあれば、useContext(SomeContext) を呼び出すコンポーネントはコンテクスト値としてその undefined を受け取ります。