useContext — это хук, который позволяет вам считывать и подписываться на контекст внутри вашего компонента.

const value = useContext(SomeContext)

Справочник

useContext(SomeContext)

Вызовите useContext на верхнем уровне вашего компонента, чтобы прочитать и подписаться на контекст.

import { useContext } from 'react';

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

Больше примеров ниже.

Параметры

  • SomeContext: Контекст, который вы создали заранее с помощью createContext. Сам по себе контекст не хранит данные. Он является представлением типа данных, которые вы можете предоставить или прочитать внутри ваших компонентов.

Возвращаемое значение

useContext возвращает значение контекста для вызывающего компонента. Оно определяется как value, переданное ближайшему SomeContext.Provider выше по дереву, чем вызывающий компонент. Если такого источника нет, то вернётся defaultValue, который вы передали в createContext для этого контекста. Возвращаемое значение всегда актуально. React автоматически повторно рендерит компоненты, которые считывают определённый контекст, при его изменении.

Замечания

  • Вызов useContext() из компонента не будет затронут источниками, возвращёнными из того же компонента. Соответствующий <Context.Provider> обязан быть выше по дереву, чем компонент, который вызывает useContext().
  • React автоматически повторно рендерит все дочерние компоненты, использующие источник, значение value которого было изменено. Предшествующее и следующее значения сравниваются, используя сравнение 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. Когда Button в любом месте внутри Form вызовет useContext(ThemeContext), он получит значение "dark".

Pitfall

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="Добро пожаловать">
      <Button>Зарегистрироваться</Button>
      <Button>Войти</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>
  );
}


Изменение данных, передаваемых через контекст

Чаще всего вам понадобится, чтобы контекст менялся со временем. Чтобы изменить контекст, используйте его вместе с состоянием. Объявите переменную состояния в родительском компоненте, и передайте текущее состояние вниз по дереву как значение контекста.

function MyPage() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value={theme}>
<Form />
<Button onClick={() => {
setTheme('light');
}}>
Поставить светлую тему
</Button>
</ThemeContext.Provider>
);
}

Теперь любой компонент Button внутри источника будет получать текущее значение theme. Если вы вызовете setTheme, чтобы изменить значение theme, которое вы передаёте в источник, все компоненты Button будут повторно отрендерены с новым зачением 'light'.

Примеры изменения контекста

Example 1 of 5:
Изменение значения через контекст

В этом примере компонент MyApp содержит переменную состояния, которая затем передаётся в источник ThemeContext. Активация чекбокса “Dark mode” изменяет состояние. Изменение предоставляемого значения вызывает повторный рендер всех компонентов, использующих данный контекст.

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')
          }}
        />
        Тёмная тема
      </label>
    </ThemeContext.Provider>
  )
}

function Form({ children }) {
  return (
    <Panel title="Добро пожаловать">
      <Button>Зарегистрироваться</Button>
      <Button>Войти</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);

Стандартное значения остаётся неизменным. Если вы хотите изменить контекст, используйте его вместе с состоянием, как описано выше.

Зачастую, вместо использования null, можно задать более содержательное стандартное значение, например:

const ThemeContext = createContext('light');

В ином случае, если вы случайно отрендерите какой-то компонент без соответствующего источника контекста, что-то может пойти не так. Такой подход также поможет вашим компонентам хорошо работать в тестовом окружении, без установки множества источников в тестах.

В примере выше кнопка “Toggle theme” всегда светлая, потому что она находится снаружи какого-либо источника контекста темы, а стандартное значение источника контекста темы — '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');
      }}>
        Переключить тему
      </Button>
    </>
  )
}

function Form({ children }) {
  return (
    <Panel title="Добро пожаловать">
      <Button>Зарегистрироваться</Button>
      <Button>Войти</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>

Вы можете вкладывать и переопределять источники сколько вам нужно.

Try out some examples

Example 1 of 2:
Переопределение темы

В данном случае, кнопка внутри компонента Footer получает значение контекста ("light"), в отличие от кнопок снаружи, которые получают ("dark").

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="Добро пожаловать">
      <Button>Зарегистрироваться</Button>
      <Button>Войти</Button>
      <ThemeContext.Provider value="light">
        <Footer />
      </ThemeContext.Provider>
    </Panel>
  );
}

function Footer() {
  return (
    <footer>
      <Button>Настройки</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>
);
}

В данном случае, значение контекста является JavaScript-объектом с двумя свойствами, одно из которых — функция. Когда произойдёт повторный рендер 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 нужен повторный рендер, компонентам, вызывающим useContext(AuthContext), не понадобится повторный рендер, если, конечно, не изменился currentUser.

Узнайте больше про useMemo и useCallback.


Устранение неполадок

Мой компонент не видит значение из источника контекста

Есть несколько распространённых причин, по которым это может происходить:

  1. Вы рендерите <SomeContext.Provider> в том же компоненте (или ниже по дереву), в котором вызываете useContext(). Сдвиньте <SomeContext.Provider> выше и наружу компонента, вызывающего useContext().
  2. Возможно, вы забыли обернуть ваш компонент в <SomeContext.Provider>, или он попал не в ту часть дерева, в которой вы его ожидали. Проверьте вашу иерархию с помощью React DevTools.
  3. Вы можете иметь дело с какой-то проблемой сборки вашими инструментами приложения, из-за которой SomeContext, как контекст из предоставляющего его компонента, и SomeContext, как контекст из читающего компонента, становятся двумя разными объектами. Это может произойти, например, при использовании символических ссылок. Вы можете проверить это, если присвоите контексты глобальным переменным, например window.SomeContext1 и window.SomeContext2, а затем написать в консоли window.SomeContext1 === window.SomeContext2. Если они различаются, эту проблему нужно исправлять на уровне инструментов сборки.

Мой контекст всегда возвращает undefined, хотя стандартное значение отличается

Возможно, вы забыли прописать value вашему источнику в дереве:

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

Если же вы забыли указать value, это то же самое, как передать в источник value={undefined}.

Также возможно, что вы по ошибке использовали другое имя пропа:

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

Если возникнет любая из этих проблем, вы увидите в консоли предупреждение от React. Чтобы исправить их, назовите проп value:

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

Заметьте, что стандартное значение вызова createContext(defaultValue) используется лишь если соответсвующего источника контекста не существует выше по дереву Если же где-то в дереве родительских компонентов есть компонент <SomeContext.Provider value={undefined}>, компонент, вызывающий useContext(SomeContext) получит undefined как значение контекста.