useContext це хук, який дозволяє читати контекст компонента та підписуватись на нього.

const value = useContext(SomeContext)

Довідка

useContext(SomeContext)

Викличте useContext на верхньому рівні компонента щоб прочитати та підписатися на контекст.

import { useContext } from 'react';

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

Дивіться інші приклади.

Параметри

  • SomeContext: це об’єкт контексту, який було створено за допомогою функції createContext. Контекст безпосередньо не містить інформацію, він лише визначає що ви можете передати або отримати з компонентів.

Повернення

Викликаючи useContext, ви отримуєте значення (value), яке відповідає контексту компонента, що викликає цей хук. Це значення визначається як 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() {
// ... рендерить кнопки всередині ...
}

Не має значення скільки шарів компонентів між провайдером та 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. Вибір прапорця “Темний режим” оновлює стан. Зміна переданого значення викликає повторний рендер усіх компонентів, які використовують цей контекст.

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');

Отже, якщо випадково відрендерите якийсь компонент без відповідного провайдера, це не призведе до помилки. І також допоможе компонентам добре працювати в тестовому середовищі, не вимагаючи налаштування провайдерів.

У прикладі нижче кнопка “Перемкнути тему” завжди буде світлою, тому що вона знаходиться поза жодним провайдером теми, і значенням теми за замовчуванням є '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>

Провайдери можна вкладати і перевизначати без обмежень.

Приклади перевизначення контексту

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 у дереві:

// 🚩 Не працює: немає властивості value
<ThemeContext.Provider>
<Button />
</ThemeContext.Provider>

Якщо ви забули вказати value, це еквівалентно передачі value={undefined}.

Також, можливо, ви випадково застосували помилкову назву властивості:

// 🚩 Не працює: властивість має називатися "value"
<ThemeContext.Provider theme={theme}>
<Button />
</ThemeContext.Provider>

В обох випадках React покаже попередження у консолі. Щоб виправити це, назвіть властивість value:

// ✅ Передача властивості value
<ThemeContext.Provider value={theme}>
<Button />
</ThemeContext.Provider>

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