useContext — работа с контекстом в React

16 марта 2026
Автор

Олег Марков

Введение

Когда приложение на React растёт, передача данных между компонентами начинает превращаться в настоящую проблему. Представьте: у вас есть информация о текущем пользователе, которая нужна в десяти разных местах — в шапке сайта, в боковом меню, в личном кабинете, в настройках. Передавать эти данные через пропсы на каждом уровне иерархии крайне неудобно и засоряет код.

Именно для решения этой проблемы в React существует Context API, а хук useContext делает работу с контекстом максимально простой и удобной. В этой статье мы подробно разберём, что такое useContext, как работает Context API в целом, рассмотрим реальные практические примеры и узнаем, когда контекст — это правильный выбор, а когда лучше обратиться к другим инструментам.

Что такое Context API и проблема prop drilling

Прежде чем разбираться с useContext, важно понять, зачем вообще нужен контекст.

Проблема prop drilling

В React данные передаются от родительского компонента к дочернему через пропсы. Это работает отлично, когда компоненты находятся рядом в иерархии. Но когда данные нужны глубоко вложенному компоненту, их приходится "пробрасывать" через все промежуточные уровни — это называется prop drilling (бурение пропсами).

// Проблема: username нужен только в UserGreeting,
// но приходится передавать через все уровни
function App() {
  const username = "Алексей";
  return <Layout username={username} />;
}

function Layout({ username }: { username: string }) {
  // Layout не использует username, но вынужден его передавать
  return <Header username={username} />;
}

function Header({ username }: { username: string }) {
  // Header тоже не использует username напрямую
  return <Navigation username={username} />;
}

function Navigation({ username }: { username: string }) {
  // И только здесь username наконец используется
  return <UserGreeting username={username} />;
}

function UserGreeting({ username }: { username: string }) {
  return <div>Привет, {username}!</div>;
}

Как видите, username проходит через три промежуточных компонента, которые его вообще не используют. Context API решает именно эту проблему.

Что такое Context

Context (контекст) — это механизм React, позволяющий передавать данные через дерево компонентов, минуя промежуточные уровни. Контекст похож на "глобальную переменную" для поддерева компонентов — любой компонент внутри может получить к ней доступ напрямую.

Как работает Context API: createContext, Provider и useContext

Context API состоит из трёх ключевых элементов: создание контекста (createContext), поставщик данных (Provider) и потребитель данных (useContext).

createContext — создание контекста

Контекст создаётся с помощью функции createContext, которой передаётся значение по умолчанию:

import { createContext } from 'react';

// Создаём контекст с начальным значением null
const UserContext = createContext<string | null>(null);

// Или с конкретным значением по умолчанию
const ThemeContext = createContext<'light' | 'dark'>('light');

Значение по умолчанию используется только тогда, когда компонент не находится внутри соответствующего Provider. На практике это редкая ситуация, но значение по умолчанию помогает при тестировании и позволяет TypeScript корректно определить тип контекста.

Provider — поставщик данных

Provider — это компонент, который "поставляет" данные контекста всем своим потомкам. Любой компонент, находящийся внутри Provider, может получить доступ к его данным.

import { createContext, useState } from 'react';

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

function App() {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');

  return (
    // Оборачиваем приложение в Provider и передаём значение через prop value
    <ThemeContext.Provider value={theme}>
      <Header />
      <Main />
      <Footer />
    </ThemeContext.Provider>
  );
}

Когда значение в Provider меняется, все компоненты, подписанные на этот контекст, автоматически перерисовываются.

useContext — получение данных из контекста

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

import { useContext } from 'react';

function ThemeButton() {
  // Передаём объект контекста в useContext — получаем его текущее значение
  const theme = useContext(ThemeContext);

  return (
    <button className={`btn btn-${theme}`}>
      Текущая тема: {theme}
    </button>
  );
}

Хук useContext автоматически подписывается на изменения контекста и вызывает повторный рендер компонента при изменении значения.

Базовый синтаксис useContext

const value = useContext(SomeContext);

Где:

  • SomeContext — объект контекста, созданный через createContext
  • value — текущее значение контекста, полученное от ближайшего Provider выше по дереву

Если компонент не находится внутри Provider, useContext вернёт значение по умолчанию, переданное в createContext.

Практические примеры использования useContext

Пример 1: Управление темой приложения

Один из классических примеров использования контекста — переключение темы (светлой/тёмной):

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

// Описываем тип для контекста темы
type Theme = 'light' | 'dark';

interface ThemeContextType {
  theme: Theme;
  toggleTheme: () => void;
}

// Создаём контекст
const ThemeContext = createContext<ThemeContextType | null>(null);

// Провайдер темы — отдельный компонент для удобства
function ThemeProvider({ children }: { children: ReactNode }) {
  const [theme, setTheme] = useState<Theme>('light');

  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// Кастомный хук для удобного доступа к контексту темы
function useTheme(): ThemeContextType {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme должен использоваться внутри ThemeProvider');
  }
  return context;
}

// Компонент-кнопка переключения темы
function ThemeToggleButton() {
  const { theme, toggleTheme } = useTheme();

  return (
    <button
      onClick={toggleTheme}
      style={{
        background: theme === 'light' ? '#fff' : '#333',
        color: theme === 'light' ? '#333' : '#fff',
        padding: '8px 16px',
        border: '1px solid currentColor',
        borderRadius: '4px',
        cursor: 'pointer',
      }}
    >
      Сейчас: {theme === 'light' ? 'Светлая' : 'Тёмная'} тема
    </button>
  );
}

// Компонент-страница, использующий тему
function PageContent() {
  const { theme } = useTheme();

  return (
    <div style={{
      minHeight: '100vh',
      background: theme === 'light' ? '#f5f5f5' : '#1a1a1a',
      color: theme === 'light' ? '#333' : '#f5f5f5',
      padding: '20px',
    }}>
      <ThemeToggleButton />
      <h1>Контент страницы</h1>
      <p>Тема применяется ко всему приложению через контекст.</p>
    </div>
  );
}

// Корневой компонент
function App() {
  return (
    <ThemeProvider>
      <PageContent />
    </ThemeProvider>
  );
}

Обратите внимание на паттерн с кастомным хуком useTheme — это лучшая практика при работе с контекстом, которую мы разберём подробнее.

Пример 2: Передача данных авторизованного пользователя

Типичный случай — передача информации о текущем пользователе по всему приложению:

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

// Тип пользователя
interface User {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'user';
}

// Тип контекста аутентификации
interface AuthContextType {
  user: User | null;
  isAuthenticated: boolean;
  login: (user: User) => void;
  logout: () => void;
}

const AuthContext = createContext<AuthContextType | null>(null);

// Провайдер аутентификации
function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<User | null>(null);

  const login = (userData: User) => {
    setUser(userData);
    // Здесь можно сохранить токен в localStorage
  };

  const logout = () => {
    setUser(null);
    // Здесь можно удалить токен из localStorage
  };

  return (
    <AuthContext.Provider value={{
      user,
      isAuthenticated: user !== null,
      login,
      logout,
    }}>
      {children}
    </AuthContext.Provider>
  );
}

// Кастомный хук для доступа к контексту аутентификации
function useAuth(): AuthContextType {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth должен использоваться внутри AuthProvider');
  }
  return context;
}

// Компонент навигации использует данные пользователя
function Navbar() {
  const { user, isAuthenticated, logout } = useAuth();

  return (
    <nav style={{ padding: '16px', borderBottom: '1px solid #eee' }}>
      <span>Логотип</span>
      {isAuthenticated ? (
        <div style={{ float: 'right' }}>
          <span>Привет, {user?.name}!</span>
          <button onClick={logout} style={{ marginLeft: '16px' }}>
            Выйти
          </button>
        </div>
      ) : (
        <a href="/login" style={{ float: 'right' }}>Войти</a>
      )}
    </nav>
  );
}

// Компонент профиля — тоже использует контекст
function ProfilePage() {
  const { user, isAuthenticated } = useAuth();

  if (!isAuthenticated) {
    return <div>Пожалуйста, войдите в систему</div>;
  }

  return (
    <div>
      <h1>Профиль</h1>
      <p>Имя: {user?.name}</p>
      <p>Email: {user?.email}</p>
      <p>Роль: {user?.role}</p>
    </div>
  );
}

function App() {
  return (
    <AuthProvider>
      <Navbar />
      <ProfilePage />
    </AuthProvider>
  );
}

Пример 3: Локализация (i18n)

Контекст отлично подходит для хранения языковых настроек:

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

type Locale = 'ru' | 'en';

// Словарь переводов
const translations = {
  ru: {
    greeting: 'Привет',
    farewell: 'До свидания',
    language: 'Язык',
  },
  en: {
    greeting: 'Hello',
    farewell: 'Goodbye',
    language: 'Language',
  },
};

type TranslationKeys = keyof typeof translations['ru'];

interface LocaleContextType {
  locale: Locale;
  t: (key: TranslationKeys) => string;
  changeLocale: (locale: Locale) => void;
}

const LocaleContext = createContext<LocaleContextType | null>(null);

function LocaleProvider({ children }: { children: ReactNode }) {
  const [locale, setLocale] = useState<Locale>('ru');

  const t = (key: TranslationKeys): string => {
    return translations[locale][key];
  };

  return (
    <LocaleContext.Provider value={{ locale, t, changeLocale: setLocale }}>
      {children}
    </LocaleContext.Provider>
  );
}

function useLocale(): LocaleContextType {
  const context = useContext(LocaleContext);
  if (!context) {
    throw new Error('useLocale должен использоваться внутри LocaleProvider');
  }
  return context;
}

// Компонент переключателя языка
function LanguageSwitcher() {
  const { locale, changeLocale, t } = useLocale();

  return (
    <div>
      <span>{t('language')}: </span>
      <button
        onClick={() => changeLocale('ru')}
        style={{ fontWeight: locale === 'ru' ? 'bold' : 'normal' }}
      >
        РУ
      </button>
      <button
        onClick={() => changeLocale('en')}
        style={{ fontWeight: locale === 'en' ? 'bold' : 'normal', marginLeft: '8px' }}
      >
        EN
      </button>
    </div>
  );
}

// Компонент использует переводы
function WelcomePage() {
  const { t } = useLocale();

  return (
    <div>
      <LanguageSwitcher />
      <h1>{t('greeting')}!</h1>
      <p>{t('farewell')}!</p>
    </div>
  );
}

Пример 4: Корзина покупок

Более сложный пример — управление корзиной интернет-магазина с использованием useReducer внутри контекста:

import { createContext, useContext, useReducer, ReactNode } from 'react';

// Типы для корзины
interface CartItem {
  id: number;
  name: string;
  price: number;
  quantity: number;
}

interface CartState {
  items: CartItem[];
  total: number;
}

type CartAction =
  | { type: 'ADD_ITEM'; payload: Omit<CartItem, 'quantity'> }
  | { type: 'REMOVE_ITEM'; payload: { id: number } }
  | { type: 'UPDATE_QUANTITY'; payload: { id: number; quantity: number } }
  | { type: 'CLEAR_CART' };

// Редьюсер для управления состоянием корзины
function cartReducer(state: CartState, action: CartAction): CartState {
  switch (action.type) {
    case 'ADD_ITEM': {
      const existingItem = state.items.find(item => item.id === action.payload.id);
      if (existingItem) {
        // Если товар уже есть, увеличиваем количество
        const updatedItems = state.items.map(item =>
          item.id === action.payload.id
            ? { ...item, quantity: item.quantity + 1 }
            : item
        );
        return {
          items: updatedItems,
          total: state.total + action.payload.price,
        };
      }
      // Если товара нет, добавляем новый
      return {
        items: [...state.items, { ...action.payload, quantity: 1 }],
        total: state.total + action.payload.price,
      };
    }
    case 'REMOVE_ITEM': {
      const itemToRemove = state.items.find(item => item.id === action.payload.id);
      return {
        items: state.items.filter(item => item.id !== action.payload.id),
        total: state.total - (itemToRemove ? itemToRemove.price * itemToRemove.quantity : 0),
      };
    }
    case 'UPDATE_QUANTITY': {
      const item = state.items.find(i => i.id === action.payload.id);
      if (!item) return state;
      const diff = action.payload.quantity - item.quantity;
      return {
        items: state.items.map(i =>
          i.id === action.payload.id
            ? { ...i, quantity: action.payload.quantity }
            : i
        ),
        total: state.total + diff * item.price,
      };
    }
    case 'CLEAR_CART':
      return { items: [], total: 0 };
    default:
      return state;
  }
}

interface CartContextType {
  state: CartState;
  addItem: (item: Omit<CartItem, 'quantity'>) => void;
  removeItem: (id: number) => void;
  clearCart: () => void;
}

const CartContext = createContext<CartContextType | null>(null);

function CartProvider({ children }: { children: ReactNode }) {
  const [state, dispatch] = useReducer(cartReducer, { items: [], total: 0 });

  const addItem = (item: Omit<CartItem, 'quantity'>) => {
    dispatch({ type: 'ADD_ITEM', payload: item });
  };

  const removeItem = (id: number) => {
    dispatch({ type: 'REMOVE_ITEM', payload: { id } });
  };

  const clearCart = () => {
    dispatch({ type: 'CLEAR_CART' });
  };

  return (
    <CartContext.Provider value={{ state, addItem, removeItem, clearCart }}>
      {children}
    </CartContext.Provider>
  );
}

function useCart(): CartContextType {
  const context = useContext(CartContext);
  if (!context) {
    throw new Error('useCart должен использоваться внутри CartProvider');
  }
  return context;
}

// Компонент кнопки добавления товара
function AddToCartButton({ product }: { product: Omit<CartItem, 'quantity'> }) {
  const { addItem } = useCart();

  return (
    <button onClick={() => addItem(product)}>
      Добавить в корзину
    </button>
  );
}

// Компонент отображения корзины
function CartWidget() {
  const { state, removeItem, clearCart } = useCart();

  return (
    <div>
      <h2>Корзина ({state.items.length} товара)</h2>
      {state.items.map(item => (
        <div key={item.id}>
          <span>{item.name} × {item.quantity}</span>
          <span> — {item.price * item.quantity} ₽</span>
          <button onClick={() => removeItem(item.id)}>✕</button>
        </div>
      ))}
      <div><strong>Итого: {state.total} ₽</strong></div>
      <button onClick={clearCart}>Очистить корзину</button>
    </div>
  );
}

Когда использовать useContext

Контекст — мощный инструмент, но использовать его нужно обдуманно. Вот ситуации, когда useContext — правильный выбор:

Используйте useContext когда:

  • Данные нужны во многих компонентах на разных уровнях иерархии
  • Вы передаёте глобальные настройки: тема, язык, валюта
  • Нужно хранить данные авторизации и информацию о пользователе
  • Реализуете паттерн "передача функций вниз" (колбэки, диспетчеры)
  • Есть несколько не связанных компонентов, которым нужны одни данные

Не используйте useContext когда:

  • Данные нужны только двум-трём соседним компонентам (передайте через пропсы)
  • Данные меняются очень часто (это вызовет множество лишних ре-рендеров)
  • Приложение небольшое и prop drilling не является проблемой

Лучшие практики

1. Создавайте кастомные хуки для контекстов

Всегда оборачивайте useContext в кастомный хук. Это делает код чище и позволяет добавить валидацию:

// Плохо — прямое использование useContext везде
function MyComponent() {
  const theme = useContext(ThemeContext); // а вдруг null?
}

// Хорошо — кастомный хук с валидацией
function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme должен использоваться внутри ThemeProvider');
  }
  return context;
}

function MyComponent() {
  const theme = useTheme(); // безопасно и понятно
}

2. Выносите Provider в отдельный компонент

Держите создание состояния и Provider в одном месте:

// Выделяем всю логику в отдельный файл ThemeProvider.tsx
export function ThemeProvider({ children }: { children: ReactNode }) {
  const [theme, setTheme] = useState<Theme>('light');
  // ... логика
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

3. Разделяйте контексты по ответственности

Не создавайте один огромный "глобальный" контекст. Лучше иметь несколько узкоспециализированных:

// Плохо — один огромный контекст со всем подряд
const AppContext = createContext({ user, theme, cart, notifications, ... });

// Хорошо — каждый контекст отвечает за свою область
const UserContext = createContext(...);
const ThemeContext = createContext(...);
const CartContext = createContext(...);

4. Мемоизируйте значение контекста

Если в Provider передаётся объект, мемоизируйте его через useMemo, чтобы избежать лишних ре-рендеров:

import { useMemo } from 'react';

function UserProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<User | null>(null);

  // Мемоизируем объект контекста — он создаётся заново только при изменении user
  const value = useMemo(() => ({
    user,
    setUser,
    isAdmin: user?.role === 'admin',
  }), [user]);

  return (
    <UserContext.Provider value={value}>
      {children}
    </UserContext.Provider>
  );
}

5. Типизируйте контексты через TypeScript

Всегда указывайте тип контекста — это предотвращает ошибки:

// Явная типизация гарантирует корректное использование
interface ThemeContextType {
  theme: 'light' | 'dark';
  toggleTheme: () => void;
}

const ThemeContext = createContext<ThemeContextType | null>(null);

Распространённые ошибки

Ошибка 1: Использование контекста вне Provider

// Ошибка: компонент не обёрнут в Provider
function App() {
  return <MyComponent />; // MyComponent использует useContext, но нет Provider!
}

// Правильно
function App() {
  return (
    <ThemeProvider>
      <MyComponent />
    </ThemeProvider>
  );
}

Ошибка 2: Обновление объекта без мемоизации

// Плохо: при каждом рендере Parent создаётся новый объект value
// Это вызывает ре-рендер ВСЕХ потребителей контекста
function Parent() {
  const [count, setCount] = useState(0);
  return (
    <MyContext.Provider value={{ count, setCount }}> {/* новый объект каждый раз! */}
      <Child />
    </MyContext.Provider>
  );
}

// Хорошо: мемоизируем объект
function Parent() {
  const [count, setCount] = useState(0);
  const value = useMemo(() => ({ count, setCount }), [count]);
  return (
    <MyContext.Provider value={value}>
      <Child />
    </MyContext.Provider>
  );
}

Ошибка 3: Помещать всё состояние в один контекст

// Плохо: изменение любого значения вызывает ре-рендер ВСЕХ потребителей
const AppContext = createContext({
  user: null,
  theme: 'light',
  notifications: [],
  cart: [],
  // ... ещё 20 полей
});

// Хорошо: разделяем по смыслу, изменения изолированы
const UserContext = createContext(null);
const ThemeContext = createContext('light');
const NotificationsContext = createContext([]);

Ошибка 4: Чрезмерное использование контекста

Не нужно переносить всё в контекст. Если данные используются только в одном месте или передаются максимум через один-два уровня — оставьте пропсы:

// Излишнее использование контекста
const ButtonTextContext = createContext('');
function MyButton() {
  const text = useContext(ButtonTextContext); // для одной кнопки — перебор
  return <button>{text}</button>;
}

// Просто используйте пропс
function MyButton({ text }: { text: string }) {
  return <button>{text}</button>;
}

useContext vs Redux и другие стейт-менеджеры

Один из самых частых вопросов: когда использовать Context API, а когда Redux, Zustand или другие библиотеки управления состоянием?

Сравнительная таблица

Критерий Context API Redux / Zustand
Размер приложения Малое и среднее Среднее и крупное
Сложность настройки Минимальная Требует конфигурации
Производительность Ре-рендер всех потребителей Точечные обновления
DevTools Нет Есть (Redux DevTools)
Middleware Нет Есть (thunk, saga)
Сложная бизнес-логика Неудобно Хорошо подходит
Зависимости Встроено в React Внешняя библиотека

Когда Context API достаточно

  • Передача темы, языка, информации о пользователе
  • Небольшие и средние приложения без сложной бизнес-логики
  • Когда не нужна история изменений состояния и time-travel отладка
  • Данные меняются относительно редко

Когда нужен Redux / Zustand

  • Очень частые обновления состояния (например, real-time данные)
  • Сложная бизнес-логика с множеством зависимостей
  • Необходима отладка через DevTools с историей изменений
  • Большая команда, где важна единая конвенция работы с состоянием
  • Нужны middleware для асинхронных операций (thunk, saga)

Комбинирование подходов

На практике часто используют оба подхода вместе:

// Redux/Zustand — для сложной бизнес-логики (корзина, заказы, фильтры)
// Context — для UI-настроек (тема, язык, sidebar открыт/закрыт)

function App() {
  return (
    <Provider store={store}> {/* Redux для бизнес-данных */}
      <ThemeProvider>        {/* Context для UI */}
        <LocaleProvider>    {/* Context для локализации */}
          <Router>
            <MainLayout />
          </Router>
        </LocaleProvider>
      </ThemeProvider>
    </Provider>
  );
}

Производительность и оптимизация

Важно понимать, как Context влияет на производительность приложения.

Как работает обновление контекста

Когда значение в Provider меняется, все компоненты, подписанные на этот контекст через useContext, будут повторно отрисованы — даже если они используют только часть данных контекста.

// Если меняется только user.name, ВСЕ три компонента перерисуются
const UserContext = createContext({ name: '', email: '', avatar: '' });

function UserName() {
  const { name } = useContext(UserContext); // перерисовывается при любом изменении
  return <span>{name}</span>;
}

function UserEmail() {
  const { email } = useContext(UserContext); // перерисовывается при любом изменении
  return <span>{email}</span>;
}

Способы оптимизации

Разделяйте контексты по частоте обновлений:

// Отдельный контекст для редко меняемых данных
const UserStaticContext = createContext({ id: 0, email: '' });

// Отдельный контекст для часто меняемых данных
const UserDynamicContext = createContext({ isOnline: false, lastSeen: null });

Используйте memo для дочерних компонентов:

import { memo } from 'react';

// Компонент не перерисовывается если его пропсы не изменились
const ExpensiveChild = memo(function ExpensiveChild({ data }: { data: string }) {
  return <div>{data}</div>;
});

Заключение

useContext — это мощный и простой инструмент для решения проблемы prop drilling в React. Он позволяет передавать данные напрямую к любому компоненту в дереве, минуя промежуточные уровни.

Ключевые моменты, которые стоит запомнить:

  • Context API состоит из трёх частей: createContext, Provider и useContext
  • Всегда создавайте кастомные хуки для доступа к контексту вместо прямого вызова useContext
  • Добавляйте валидацию в кастомные хуки — бросайте ошибку, если компонент вне Provider
  • Мемоизируйте объекты значений контекста через useMemo
  • Разделяйте контексты по ответственности и частоте обновлений
  • Используйте Context для глобальных UI-настроек, но рассмотрите Redux/Zustand для сложной бизнес-логики

Context API прекрасно подходит для среднего масштаба задач и при правильном применении значительно упрощает архитектуру приложения.

Если вы хотите глубоко разобраться в работе с состоянием React, включая Context API, Redux Toolkit и другие инструменты управления данными — приходите на курс Основы React, React Router и Redux Toolkit. На курсе 177 уроков и 17 упражнений, AI-тренажеры для безлимитной практики с кодом и задачами 24/7, решение задач с живым ревью наставника, еженедельные встречи с менторами.

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

Почему мой компонент перерисовывается, хотя нужное мне значение не изменилось?

Это происходит потому, что useContext подписывает компонент на весь контекст, а не на его часть. Если в контексте хранится объект и любое его поле меняется, все потребители перерисовываются. Решения: разделить контекст на несколько, мемоизировать значения через useMemo, или использовать библиотеку use-context-selector.

Можно ли использовать несколько контекстов одновременно в одном компоненте?

Да, можно вызывать несколько useContext в одном компоненте:

function MyComponent() {
  const theme = useTheme();
  const { user } = useAuth();
  const { t } = useLocale();
  // ...
}

Как обновить контекст из глубоко вложенного компонента?

Передайте функцию-обновления в значении контекста:

const CounterContext = createContext<{ count: number; increment: () => void } | null>(null);

function CounterProvider({ children }: { children: ReactNode }) {
  const [count, setCount] = useState(0);
  const value = useMemo(() => ({
    count,
    increment: () => setCount(c => c + 1),
  }), [count]);

  return <CounterContext.Provider value={value}>{children}</CounterContext.Provider>;
}

// В любом дочернем компоненте
function DeepChild() {
  const { increment } = useContext(CounterContext)!;
  return <button onClick={increment}>+1</button>;
}

Как тестировать компоненты, использующие useContext?

Оберните тестируемый компонент в нужный Provider с тестовыми данными:

import { render, screen } from '@testing-library/react';

test('отображает имя пользователя', () => {
  render(
    <UserContext.Provider value={{ user: { name: 'Тест', email: 'test@example.com' } }}>
      <UserProfile />
    </UserContext.Provider>
  );
  expect(screen.getByText('Тест')).toBeInTheDocument();
});

Можно ли использовать контекст вне React-компонента (в обычных JS-функциях)?

Нет, useContext работает только внутри функциональных компонентов или кастомных хуков. Если нужен доступ к данным вне React (например, в API-сервисах), используйте отдельные модули или такие инструменты как Redux, Zustand — они предоставляют доступ к состоянию вне компонентов через getState() или хранилища.

Что произойдёт, если использовать useContext без Provider?

useContext вернёт значение по умолчанию, переданное в createContext. Если использовать паттерн с null по умолчанию и валидацией в кастомном хуке, вы получите понятную ошибку. Без валидации вы получите null и, скорее всего, ошибку при попытке обратиться к свойствам.

Стрелочка влевоuseEffect в React что это и как использоватьОптимизация рендеринга в React: от теории к глубокой практикеСтрелочка вправо

Постройте личный план изучения React до уровня Middle — бесплатно!

React — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по React

Uncontrolled Components: когда DOM управляет даннымиRender Props: гибкое управление рендерингом в ReactПрофилирование React: как найти и устранить узкие местаЧастичное применение: как создавать компоненты без лишнего кодаЛенивая загрузка: как ускорить React-приложение в разыHOC в React: мастерство композиции компонентовuseMemo: как спасти производительность от тяжелых вычисленийError Boundaries: создаем надежные React-приложенияКонтролируемые компоненты в React: полный контроль над формамиCompound Components в React: создаем гибкие компоненты с мощным APIКомпозиция компонентов в React: строим гибкие интерфейсыCode Splitting в React: как уменьшить бандл и ускорить загрузку приложенияАсинхронные компоненты в React: новый стандарт работы с данными
useState в React что это и как использоватьuseTransition - плавные переходы между состояниямиuseSyncExternalStore — работа с внешними сторамиuseRef в React — создание ссылок на DOM и значенияuseLayoutEffect в React — эффект до отрисовкиuseInsertionEffect — внедрение стилей до мутаций DOMuseImperativeHandle в React — настройка ref дочернего компонентаuseId — генерация уникальных идентификаторовuseDeferredValue — отложенное обновление состоянияuseDebugValue — отладка кастомных хуковuseCallback в React — мемоизация функцийuseReducer — альтернатива useState для сложной логикиuseMemo в React: как и когда оптимизировать тяжелые вычисленияuseEffect в React что это и как использоватьuseContext — работа с контекстом в ReactОптимизация рендеринга в React: от теории к глубокой практикеЧто такое useRef и как его применять в ReactКак и зачем использовать React HooksУправление состоянием в React через ContextКак предотвратить лишние ре-рендеры в React: полное руководствоuseMemo vs useCallback: подробное руководство по мемоизации в ReactПравила хуков — правила использованияuseEffect vs useLayoutEffect: в чём разница и какой хук выбрать?Кастомные хуки в React — создание собственных хуковuseState продвинутое использование в React
StrictMode в React — как находить ошибки на этапе разработкиСерверные компоненты React (RSC) — подробный разбор и практикаКак работает рендеринг в ReactЧто такое props в React и как их правильно использоватьКак работает JSX связка React и HTMLЧто такое React.js и как его использоватьКак использовать элементы в ReactКак использовать React DOM в проектеЧто такое компоненты в React и как их применятьРабота с children в ReactПорталы в React: рендер компонентов вне иерархии DOMFragment в React: группировка элементов без лишних узлов DOM
Открыть базу знаний

Лучшие курсы по теме

изображение курса

React и Redux Toolkit

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий