React Context для многих стал привычным способом управления состоянием, заменив собой Redux. В этой статье вы узнаете о React Context и научитесь его использовать.
Рассмотрим работу Context на примере такого дерева. Нижние блоки можно представить как отдельные компоненты:
Допустим, вам нужно добавить свойство какому-то из нижних блоков — это несложно. Но что делать, если это свойство нужно передать соседнему (т. е. отдельному) блоку? Пока единственное решение — передать это свойство блоку-родителю, откуда его можно передать необходимому дочернему блоку.
Если вдруг вам понадобится передать свойство соседнему родителю, вы, опять же, просто переносите его на уровень выше, а потом «спускаете» обратно к нужному блоку.
Решение довольно простое, а главное — рабочее. Но что делать, если нужно передать свойство дальнему блоку?
Для этого нужно «поднять» свойство по всему дереву вверх до самого первого блока, а потом «спустить» обратно к нужному дочернему блоку. Проблема в том, что это свойство будет проходить через кучу промежуточных компонентов. Этот утомительный и трудоёмкий процесс известен как пробрасывание (англ. prop drilling).
Именно на этом этапе задействуется Context API. Он даёт возможность передавать свойства отдельным блокам дерева без сложных манипуляций с родительскими и дочерними блоками.
В качестве примера использования React Context возьмём вот такой забавный переключатель дня и ночи:
Полный код можно посмотреть здесь.
Вначале нужно сделать так, чтобы всё приложение имело доступ к Context. Для этого в index.js
нужно обернуть всё приложение в ThemeContext.Provider
. Ещё стоит передать ему свойство value
. В нём будет храниться состояние: день или ночь.
import React from "react";
import ReactDOM from "react-dom";
import ThemeContext from "./themeContext";
import App from "./App";
ReactDOM.render(
<ThemeContext.Provider value={"Day"}>
<App />
</ThemeContext.Provider>,
document.getElementById("root")
);
Получение свойств от Context через contextType
Пока что в App.js
возвращается компонент <Image />
.
import React from "react";
import Image from "./Image";
class App extends React.Component {
render() {
return (
<div className="app">
<Image />
</div>
);
}
}
export default App;
Нам нужно с помощью Context менять className
в Image.js
с Day
на Night
и обратно. Для этого нужно добавить к компоненту статическое свойство ContextType
. Потом, используя интерполяцию строки, нужно передать это свойство в className
в объекте <Image />
.
Теперь свойство className
содержит строку из value
:
import React from "react";
import Button from "./Button";
import ThemeContext from "./themeContext";
class Image extends React.Component {
render() {
const theme = this.context;
return (
<div className={`${theme}-image image`}>
<div className={`${theme}-ball ball`} />
<Button />
</div>
);
}
}
Image.contextType = ThemeContext;
export default Image;
Получение свойств из Context
К сожалению, способ выше работает только с классовыми компонентами. Но благодаря хукам с помощью функциональных компонентов теперь можно сделать всё что угодно. Так что для полноты картины нужно конвертировать имеющиеся компоненты в функциональные и использовать ThemeContext.Consumer
, чтобы передать информацию между ними.
Это можно сделать, обернув элементы в экземпляр <ThemeContext.Consumer>
. Внутри него нужно предоставить функцию, возвращающую элементы. В данном случае будет использоваться паттерн «render props», который позволяет передать компоненту в качестве children любую функцию, которая возвращает JSX код.
import React from "react";
import Button from "./Button";
import ThemeContext from "./themeContext";
function Image(props) {
// Это больше не нужно
// const theme = this.context
return (
<ThemeContext.Consumer>
{theme => (
<div className={`${theme}-image image`}>
<div className={`${theme}-ball ball`} />
<Button />
</div>
)}
</ThemeContext.Consumer>
);
}
// Это больше не нужно
// Image.contextType = ThemeContext;
export default Image;
Примечание <Button />
тоже нужно обернуть в <ThemeContext.Consumer>
— в будущем это добавит функциональности кнопке.
import React from "react";
import ThemeContext from "./themeContext";
function Button(props) {
return (
<ThemeContext.Consumer>
{context => (
<button className="button">
Switch
<span role="img" aria-label="sun">
?
</span>
<span role="img" aria-label="moon">
?
</span>
</button>
)}
</ThemeContext.Consumer>
);
}
export default Button;
Вынесение свойств из Context
На текущем этапе в приложении передаётся заранее прописанное значение, но наша цель — переключать день и ночь кнопкой. Для этого нужно переместить <Provider>
в отдельный файл и обернуть его в собственный компонент ThemeContextProvider
.
import React, { Component } from "react";
const { Provider, Consumer } = React.createContext();
class ThemeContextProvider extends Component {
render() {
return <Provider value={"Day"}>{this.props.children}</Provider>;
}
}
export { ThemeContextProvider, Consumer as ThemeContextConsumer };
Примечание Теперь свойство value
обрабатывается и в новом файле ThemeContext.js
, поэтому обработку этого значения из файла index.js
нужно убрать.
Изменение Context
Чтобы подвязать кнопку, сначала нужно добавить состояния state
в ThemeContextProvider
:
import React, { Component } from "react";
const { Provider, Consumer } = React.createContext();
// Примечание: ещё вы можете использовать хуки, чтобы определять состояние
// и преобразовывать его в функциональный компонент
class ThemeContextProvider extends Component {
state = {
theme: "Day"
};
render() {
return <Provider value={"Day"}>{this.props.children}</Provider>;
}
}
export { ThemeContextProvider, Consumer as ThemeContextConsumer };
Потом нужно добавить метод переключения между днём и ночью:
toggleTheme = () => {
this.setState(prevState => {
return {
theme: prevState.theme === "Day" ? "Night" : "Day"
};
});
};
После этого нужно изменить значение value
на this.state.theme
, чтобы свойство устанавливалось из состояния:
render() {
return <Provider value={this.state.theme}>{this.props.children}</Provider>;
}
Теперь нужно изменить value
на объект, содержащий {theme: this.state.theme, toggleTheme: this.toggleTheme}
, а также заменить использование value
на получение поля theme
из объекта. То есть нужно каждое theme
заменить на context
, а каждую ссылку на theme
— на context.theme
.
И под конец на кнопку нужно повесить слушатель события onClick
. При нажатии кнопки должен вызываться context.toggleTheme
— в таком случае будут обновляться Consumer’ы, которые используют состояние от Provider’ов. Код кнопки будет выглядеть примерно так:
import React from "react";
import { ThemeContextConsumer } from "./themeContext";
function Button(props) {
return (
<ThemeContextConsumer>
{context => (
<button onClick={context.toggleTheme} className="button">
Switch
<span role="img" aria-label="sun">
?
</span>
<span role="img" aria-label="moon">
?
</span>
</button>
)}
</ThemeContextConsumer>
);
}
export default Button
Теперь эта кнопка переключает день и ночь.
Рекомендации к работе с Context
Хоть в этом коде всё работает отлично, всё же есть некоторые аспекты с работой Context:
- Не используйте Context, если он заменяет пробрасывание всего на один-два уровня. Этот инструмент — отличный способ, если нужно распространить состояние на множество компонентов, находящихся в «дереве» далеко друг от друга. Но если вам нужно просто опуститься или подняться на пару уровней, то пробрасывание будет легче и быстрее.
- Постарайтесь не использовать Context для сохранения локального состояния. Например, если вам нужно сохранить введённые в форму данные, то лучше использовать локальное свойство.
- Всегда оборачивайте родителя в Provider’а на как можно более низком уровне — не стоит использовать самую верхушку «дерева».
- Наконец, если вы решили пересылать свойства таким способом, важно помнить про наблюдение за производительностью и рефакторингом. Но это скорее всего не понадобится, если просадки в производительности не будут сильно заметны.
Подумываете освоить или освежить знания по React? Тогда держите дорожную карту по React-разработке.
Перевод статьи «React Context in 5 Minutes»
Контекст¶
Контекст позволяет передавать данные через дерево компонентов без необходимости передавать пропсы на промежуточных уровнях.
В типичном React-приложении данные передаются сверху вниз (от родителя к дочернему компоненту) с помощью пропсов. Однако, этот способ может быть чересчур громоздким для некоторых типов пропсов (например, выбранный язык, UI-тема), которые необходимо передавать во многие компоненты в приложении. Контекст предоставляет способ делиться такими данными между компонентами без необходимости явно передавать пропсы через каждый уровень дерева.
- Когда использовать контекст
- Перед тем, как вы начнёте использовать контекст
- API
- React.createContext
- Context.Provider
- Class.contextType
- Context.Consumer
- Примеры
- Динамический контекст
- Изменение контекста из вложенного компонента
- Использование нескольких контекстов
- Предостережения
- Устаревший API
Когда использовать контекст¶
Контекст разработан для передачи данных, которые можно назвать «глобальными» для всего дерева React-компонентов (например, текущий аутентифицированный пользователь, UI-тема или выбранный язык). В примере ниже мы вручную передаём проп theme
, чтобы стилизовать компонент Button:
class App extends React.Component {
render() {
return <Toolbar theme="dark" />;
}
}
function Toolbar(props) {
// highlight-range{1-5,8}
// Компонент Toolbar должен передать проп "theme" ниже,
// фактически не используя его. Учитывая, что у вас в приложении
// могут быть десятки компонентов, использующих UI-тему,
// вам придётся передавать проп "theme" через все компоненты.
// И в какой-то момент это станет большой проблемой.
return (
<div>
<ThemedButton theme={props.theme} />
</div>
);
}
class ThemedButton extends React.Component {
render() {
return <Button theme={this.props.theme} />;
}
}
Контекст позволяет избежать передачи пропсов в промежуточные компоненты:
// highlight-range{1-5}
// Контекст позволяет передавать значение глубоко
// в дерево компонентов без явной передачи пропсов
// на каждом уровне. Создадим контекст для текущей
// UI-темы (со значением "light" по умолчанию).
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// highlight-range{1-4,6}
// Компонент Provider используется для передачи текущей
// UI-темы вниз по дереву. Любой компонент может использовать
// этот контекст и не важно, как глубоко он находится.
// В этом примере мы передаём "dark" в качестве значения контекста.
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// highlight-range{1,2}
// Компонент, который находится в середине,
// больше не должен явно передавать тему вниз.
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// highlight-range{1-4,7}
// Определяем contextType, чтобы получить значение контекста.
// React найдёт (выше по дереву) ближайший Provider-компонент,
// предоставляющий этот контекст, и использует его значение.
// В этом примере значение UI-темы будет "dark".
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
Перед тем, как вы начнёте использовать контекст¶
Обычно контекст используется, если необходимо обеспечить доступ данных во многих компонентах на разных уровнях вложенности. По возможности не используйте его, так как это усложняет повторное использование компонентов.
Если вы хотите избавиться от передачи некоторых пропсов на множество уровней вниз, обычно композиция компонентов является более простым решением, чем контекст.
Например, давайте рассмотрим компонент Page
, который передаёт пропсы user
и avatarSize
на несколько уровней вниз, чтобы глубоко вложенные компоненты Link
и Avatar
смогли их использовать:
<Page user={user} avatarSize={avatarSize} />
// ... который рендерит ...
<PageLayout user={user} avatarSize={avatarSize} />
// ... который рендерит ...
<NavigationBar user={user} avatarSize={avatarSize} />
// ... который рендерит ...
<Link href={user.permalink}>
<Avatar user={user} size={avatarSize} />
</Link>
Передача пропсов user
и avatarSize
вниз выглядит избыточной, если в итоге их использует только компонент Avatar
. Так же плохо, если компоненту Avatar
вдруг потребуется больше пропсов сверху, тогда вам придётся добавить их на все промежуточные уровни.
Один из способов решить эту проблему без контекста — передать вниз сам компонент Avatar
, в случае чего промежуточным компонентам не нужно знать о пропсах user
и avatarSize
:
function Page(props) {
const user = props.user;
const userLink = (
<Link href={user.permalink}>
<Avatar user={user} size={props.avatarSize} />
</Link>
);
return <PageLayout userLink={userLink} />;
}
// Теперь, это выглядит так:
<Page user={user} avatarSize={avatarSize}/>
// ... который рендерит ...
<PageLayout userLink={...} />
// ... который рендерит ...
<NavigationBar userLink={...} />
// ... который рендерит ...
{props.userLink}
С этими изменениями, только корневой компонент Page
знает о том, что компоненты Link
и Avatar
используют user
и avatarSize
.
Этот способ может сделать ваш код чище во многих случаях, уменьшая количество пропсов, которые вы должны передавать через ваше приложение, и давая больше контроля корневым компонентам. Однако, это решение не является верным в каждом случае. Перемещая больше сложной логики вверх по дереву, вы перегружаете вышестоящие компоненты.
Вы не ограничены в передаче строго одного компонента. Вы можете передать несколько дочерних компонентов или, даже, создать для них разные «слоты», как показано здесь:
function Page(props) {
const user = props.user;
const content = <Feed user={user} />;
const topBar = (
<NavigationBar>
<Link href={user.permalink}>
<Avatar user={user} size={props.avatarSize} />
</Link>
</NavigationBar>
);
return <PageLayout topBar={topBar} content={content} />;
}
Этого паттерна достаточно для большинства случаев, когда вам необходимо отделить дочерний компонент от его промежуточных родителей. Вы можете пойти ещё дальше, используя рендер-пропсы, если дочерним компонентам необходимо взаимодействовать с родителем перед рендером.
Однако, иногда одни и те же данные должны быть доступны во многих компонентах на разных уровнях дерева и вложенности. Контекст позволяет распространить эти данные и их изменения на все компоненты ниже по дереву. Управление текущим языком, UI темой или кешем данных — это пример тех случаев, когда реализация с помощью контекста будет проще использования альтернативных подходов.
API¶
React.createContext¶
const MyContext = React.createContext(defaultValue);
Создание объекта Context. Когда React рендерит компонент, который подписан на этот объект, React получит текущее значение контекста из ближайшего подходящего Provider
выше в дереве компонентов.
Аргумент defaultValue
используется только в том случае, если для компонента нет подходящего Provider
выше в дереве. Это может быть полезно для тестирования компонентов в изоляции без необходимости оборачивать их. Обратите внимание: если передать undefined
как значение Provider
, компоненты, использующие этот контекст, не будут использовать defaultValue
.
Context.Provider¶
<MyContext.Provider value={/* некоторое значение */}>
Каждый объект Контекста используется вместе с Provider
компонентом, который позволяет дочерним компонентам, использующим этот контекст, подписаться на его изменения.
Принимает проп value
, который будут передан во все компоненты, использующие этот контекст и являющиеся потомками этого Provider компонента. Один Provider может быть связан с несколькими компонентами, потребляющими контекст. Так же Provider компоненты могут быть вложены друг в друга, переопределяя значение контекста глубже в дереве.
Все потребители, которые являются потомками Provider, будут повторно рендериться, как только проп value
у Provider изменится. Потребитель перерендерится при изменении контекста, даже если его родитель, не использующий данный контекст, блокирует повторные рендеры с помощью shouldComponentUpdate
.
Изменения определяются с помощью сравнения нового и старого значения, используя алгоритм, аналогичный Object.is
.
Примечание
Способ, по которому определяются изменения, может вызвать проблемы при передаче объекта в
value
: смотрите Предостережения.
Class.contextType¶
class MyClass extends React.Component {
componentDidMount() {
let value = this.context;
/* выполнить побочный эффект на этапе монтирования, используя значение MyContext */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* отрендерить что-то, используя значение MyContext */
}
}
MyClass.contextType = MyContext;
В свойство класса contextType
может быть назначен объект контекста, созданный с помощью React.createContext()
. Это позволяет вам использовать ближайшее и актуальное значение указанного контекста при помощи this.context
. В этом случае вы получаете доступ к контексту, как во всех методах жизненного цикла, так и в рендер методе.
Примечание
Вы можете подписаться только на один контекст, используя этот API. В случае, если вам необходимо использовать больше одного, смотрите Использование нескольких контекстов.
Если вы используете экспериментальный синтаксис публичных полей класса, вы можете использовать static поле класса, чтобы инициализировать ваш
contextType
.
class MyClass extends React.Component {
static contextType = MyContext;
render() {
let value = this.context;
/* отрендерить что-то, используя значение MyContext */
}
}
Context.Consumer¶
<MyContext.Consumer>
{value => /* отрендерить что-то, используя значение контекста */}
</MyContext.Consumer>
Consumer
— это React-компонент, который подписывается на изменения контекста. В свою очередь, это позволяет вам подписаться на контекст в функциональном компоненте.
Consumer
принимает функцию в качестве дочернего компонента. Эта функция принимает текущее значение контекста и возвращает React-компонент. Передаваемый аргумент value
будет равен ближайшему (вверх по дереву) значению этого контекста, а именно пропу value
Provider компонента. Если такого Provider компонента не существует, аргумент value
будет равен значению defaultValue
, которое было передано в createContext()
.
Примечание
Подробнее про паттерн «функция как дочерний компонент» можно узнать на странице Рендер-пропсы.
Примеры¶
Динамический контекст¶
Более сложный пример динамических значений для UI темы:
theme-context.js
export const themes = {
light: {
foreground: '#000000',
background: '#eeeeee',
},
dark: {
foreground: '#ffffff',
background: '#222222',
},
};
// highlight-range{1-3}
export const ThemeContext = React.createContext(
themes.dark // значение по умолчанию
);
themed-button.js
import { ThemeContext } from './theme-context';
class ThemedButton extends React.Component {
// highlight-range{3,12}
render() {
let props = this.props;
let theme = this.context;
return (
<button
{...props}
style={{ backgroundColor: theme.background }}
/>
);
}
}
ThemedButton.contextType = ThemeContext;
export default ThemedButton;
app.js
import { ThemeContext, themes } from './theme-context';
import ThemedButton from './themed-button';
// Промежуточный компонент, который использует ThemedButton
function Toolbar(props) {
return (
<ThemedButton onClick={props.changeTheme}>
Change Theme
</ThemedButton>
);
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
theme: themes.light,
};
this.toggleTheme = () => {
this.setState((state) => ({
theme:
state.theme === themes.dark
? themes.light
: themes.dark,
}));
};
}
render() {
//highlight-range{1-4}
// ThemedButton внутри ThemeProvider использует
// значение светлой UI-темы из состояния, в то время как
// ThemedButton, который находится вне ThemeProvider,
// использует тёмную UI-тему из значения по умолчанию
//highlight-range{3-5,7}
return (
<Page>
<ThemeContext.Provider value={this.state.theme}>
<Toolbar changeTheme={this.toggleTheme} />
</ThemeContext.Provider>
<Section>
<ThemedButton />
</Section>
</Page>
);
}
}
ReactDOM.render(<App />, document.root);
Изменение контекста из вложенного компонента¶
Довольно часто необходимо изменить контекст из компонента, который находится где-то глубоко в дереве компонентов. В этом случае вы можете добавить в контекст функцию, которая позволит потребителям изменить значение этого контекста:
theme-context.js
// Убедитесь, что форма значения по умолчанию,
// передаваемого в createContext, совпадает с формой объекта,
// которую ожидают потребители контекста.
// highlight-range{2-3}
export const ThemeContext = React.createContext({
theme: themes.dark,
toggleTheme: () => {},
});
theme-toggler-button.js
import { ThemeContext } from './theme-context';
function ThemeTogglerButton() {
// highlight-range{1-2,5}
// ThemeTogglerButton получает из контекста
// не только значение UI-темы, но и функцию toggleTheme.
return (
<ThemeContext.Consumer>
{({ theme, toggleTheme }) => (
<button
onClick={toggleTheme}
style={{ backgroundColor: theme.background }}
>
Toggle Theme
</button>
)}
</ThemeContext.Consumer>
);
}
export default ThemeTogglerButton;
app.js
import { ThemeContext, themes } from './theme-context';
import ThemeTogglerButton from './theme-toggler-button';
class App extends React.Component {
constructor(props) {
super(props);
this.toggleTheme = () => {
this.setState((state) => ({
theme:
state.theme === themes.dark
? themes.light
: themes.dark,
}));
};
// highlight-range{1-2,5}
// Состояние хранит функцию для обновления контекста,
// которая будет также передана в Provider-компонент.
this.state = {
theme: themes.light,
toggleTheme: this.toggleTheme,
};
}
render() {
// highlight-range{1,3}
// Всё состояние передаётся в качестве значения контекста
return (
<ThemeContext.Provider value={this.state}>
<Content />
</ThemeContext.Provider>
);
}
}
function Content() {
return (
<div>
<ThemeTogglerButton />
</div>
);
}
ReactDOM.render(<App />, document.root);
Использование нескольких контекстов¶
Чтобы последующие рендеры (связанные с контекстом) были быстрыми, React делает каждого потребителя контекста отдельным компонентом в дереве.
// Контекст UI-темы, со светлым значением по умолчанию
const ThemeContext = React.createContext('light');
// Контекст активного пользователя
const UserContext = React.createContext({
name: 'Guest',
});
class App extends React.Component {
render() {
const { signedInUser, theme } = this.props;
// Компонент App, который предоставляет начальные значения контекстов
// highlight-range{2-3,5-6}
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={signedInUser}>
<Layout />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
}
function Layout() {
return (
<div>
<Sidebar />
<Content />
</div>
);
}
// Компонент, который может использовать несколько контекстов
function Content() {
// highlight-range{2-10}
return (
<ThemeContext.Consumer>
{(theme) => (
<UserContext.Consumer>
{(user) => (
<ProfilePage user={user} theme={theme} />
)}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
);
}
Если два или более значений контекста часто используются вместе, возможно, вам стоит рассмотреть создание отдельного компонента, который будет передавать оба значения дочерним компонентам с помощью паттерна «рендер-пропс».
Предостережения¶
Контекст использует сравнение по ссылкам, чтобы определить, когда запускать последующий рендер. Из-за этого существуют некоторые подводные камни, например, случайные повторные рендеры потребителей, при перерендере родителя Provider-компонента. В следующем примере будет происходить повторный рендер потребителя каждый повторный рендер Provider-компонента, потому что новый объект, передаваемый в value
, будет создаваться каждый раз:
class App extends React.Component {
render() {
// highlight-range{2}
return (
<MyContext.Provider
value={{ something: 'something' }}
>
<Toolbar />
</MyContext.Provider>
);
}
}
Один из вариантов решения этой проблемы — хранение этого объекта в состоянии родительского компонента:
class App extends React.Component {
constructor(props) {
super(props);
// highlight-range{2}
this.state = {
value: { something: 'something' },
};
}
render() {
// highlight-range{2}
return (
<Provider value={this.state.value}>
<Toolbar />
</Provider>
);
}
}
Устаревший API¶
Примечание
В прошлом React имел только экспериментальный API контекста. Старый API будет поддерживаться во всех 16.x релизах, но использующие его приложения должны перейти на новую версию. Устаревший API будет удалён в будущем крупном релизе React.
От автора: контекст React предоставляет данные компонентам независимо от того, насколько глубоко они находятся в дереве компонентов. Контекст используется для управления глобальными данными, например, глобальным состоянием, темой, службами, пользовательскими настройками и т.д. В этом посте вы узнаете, как использовать концепцию контекста в React.
1. Как использовать контекст
Для использования контекста в React требуется 3 простых шага: создание контекста, предоставление контекста и использование контекста.
А. Создание контекста
Встроенная функция createContext(default) создает экземпляр контекста:
import { createContext } from ‘react’; const Context = createContext(‘Default Value’); |
Хотите узнать, что необходимо для создания сайта?
Посмотрите видео и узнайте пошаговый план по созданию сайта с нуля!
Смотреть видео
Функция принимает один необязательный аргумент: значение по умолчанию.
Б. Предоставление контекста
Компонент сontext.Provider, доступный в экземпляре контекста, используется для предоставления контекста его дочерним компонентам, независимо от того, насколько они глубоки. Чтобы установить значение контекста, используйте свойство value, доступное через <Context.Provider value={value} />:
function Main() { const value = ‘My Context Value’; return ( <Context.Provider value={value}> <MyComponent /> </Context.Provider> ); } |
Если вы хотите изменить значение контекста, просто обновите свойство value.
C. Использование контекста
Использование контекста может быть выполнено двумя способами. Первый способ, который я рекомендую, — это использовать React хук useContext(Context):
import { useContext } from ‘react’; function MyComponent() { const value = useContext(Context); return <span>{value}</span>; } |
Попробуйте демо.
Хук возвращает значение контекста: value = useContext(Context). Также он гарантирует повторный рендеринг компонента при изменении значения контекста.
Второй способ — использовать функцию рендеринга, предоставленную в качестве дочернего компонента Context.Consumer, доступного в экземпляре контекста:
function MyComponent() { return ( <Context.Consumer> {value => <span>{value}</span>} </Context.Consumer> ); } |
Попробуйте демо.
Опять же, в случае, если значение контекста изменится, функция рендеринга <Context.Consumer> будет повторно визуализирована.
Вы можете иметь столько потребителей, сколько хотите для одного контекста. Если значение контекста изменяется (путем изменения свойства value в <Context.Provider value={value} />), все потребители немедленно уведомляются и повторно обрабатываются.
Если потребитель не заключен внутри провайдера, но все же пытается получить доступ к значению контекста (используя useContext(Context) или <Context.Consumer>), тогда значение контекста будет аргументом значения по умолчанию, предоставленным функции createContext(defaultValue), которая создала контекст.
2. Когда вам нужен контекст?
Основная идея использования контекста — предоставить вашим компонентам доступ к некоторым глобальным данным и повторный рендеринг при изменении этих данных. Контекст решает проблему когда вам нужно передать свойства от родителей потомкам.
Вы можете держать внутри контекста:
глобальное состояние
тему
конфигурацию приложения
аутентифицированное имя пользователя
пользовательские настройки
предпочтительный язык
набор услуг
С другой стороны, вы должны хорошо подумать, прежде чем принимать решение об использовании контекста в своем приложении.
Во-первых, интеграция контекста добавляет сложности. Создание контекста, обертывание его в провайдере, использование useContext() в каждом потребителе — это увеличивает сложность.
Во-вторых, добавление контекста затрудняет модульное тестирование компонентов. Во время модульного тестирования вам придется заключить потребительские компоненты в поставщик контекста, включая компоненты, на которые косвенно влияет контекст — предков потребителей контекста!
3. Пример использования: глобальное имя пользователя.
Самый простой способ передать данные от родителя к дочернему компоненту — это когда родитель присваивает свойства своему дочернему компоненту:
function Application() { const userName = «John Smith»; return <UserInfo userName={userName} />; } function UserInfo({ userName }) { return <span>{userName}</span>; } |
Хотите узнать, что необходимо для создания сайта?
Посмотрите видео и узнайте пошаговый план по созданию сайта с нуля!
Смотреть видео
Родительский компонент <Application /> присваивает данные userName своему дочернему компоненту <UserInfo name={userName} /> с помощью свойства userName.
Это обычный способ передачи данных с использованием свойств. Вы можете использовать этот подход без проблем.
Ситуация меняется, когда дочерний компонент <UserInfo /> не является прямым потомком <Application />, но содержится в нескольких предках.
Например, предположим, что компонент <Application /> (тот, у которого есть глобальные данные userName) визуализирует компонент <Layout />, который, в свою очередь, визуализирует компонент <Header />, который, в свою очередь, наконец, визуализирует компонент <UserInfo /> (который хотел бы получить доступ к userName). Вот как будет выглядеть такое структурирование:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
function Application() { const userName = «John Smith»; return ( <Layout userName={userName}> Main content </Layout> ); } function Layout({ children, userName }) { return ( <div> <Header userName={userName} /> <main> {children} </main> </div> ) } function Header({ userName }) { return ( <header> <UserInfo userName={userName} /> </header> ); } function UserInfo({ userName }) { return <span>{userName}</span>; } |
Попробуйте демо.
Вы можете заметить проблему: компонент <UserInfo /> отображается глубоко в дереве, и все родительские компоненты (<Layout /> и <Header />) должны передавать свойство userName. Эта проблема также известна как дриллинг свойств.
Контекст React — возможное решение. Давайте посмотрим, как его применить в следующем разделе.
3.1 Контекст в помощь
Напоминаем, что для применения контекста React требуются 3 участника: контекст, поставщик, извлеченный из контекста, и потребитель. Вот как будет выглядеть пример приложения при применении к нему контекста:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
import { useContext, createContext } from ‘react’; const UserContext = createContext(‘Unknown’); function Application() { const userName = «John Smith»; return ( <UserContext.Provider value={userName}> <Layout> Main content </Layout> </UserContext.Provider> ); } function Layout({ children }) { return ( <div> <Header /> <main> {children} </main> </div> ); } function Header() { return ( <header> <UserInfo /> </header> ); } function UserInfo() { const userName = useContext(UserContext); return <span>{userName}</span>; } |
Попробуйте демо.
Разберемся подробнее, что было сделано. Сначала создается контекст const UserContext = createContext(‘Unknown’), в котором будет храниться информация об имени пользователя.
Потом, внутри компонента <Application />, дочерние компоненты помещаются внутри провайдера контекста пользователя: <UserContext.Provider value={userName}>. Обратите внимание на свойство value компонента поставщика: именно так вы устанавливаете значение контекста.
Наконец, <UserInfo /> становится потребителем контекста с помощью встроенного хука useContext(UserContext). Хук вызывается с контекстом в качестве аргумента и возвращает значение имени пользователя.
Промежуточные компоненты <Layout /> и <Header /> не должны передаваться свойством userName. В этом большое преимущество контекста: он снимает бремя передачи данных через промежуточные компоненты.
3.2 При изменении контекста
Когда значение контекста изменяется путем изменения свойства value поставщика контекста (<Context.Provider value={value} />), все его потребители уведомляются и повторно обрабатываются.
Например, если я изменю имя пользователя с ‘John Smith’ на ‘Smith, John Smith’, то потребитель <UserInfo /> немедленно выполнит повторный рендеринг, чтобы отобразить последнее значение контекста:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import { createContext, useEffect, useState } from ‘react’; const UserContext = createContext(‘Unknown’); function Application() { const [userName, setUserName] = useState(‘John Smith’); useEffect(() => { setTimeout(() => { setUserName(‘Smith, John Smith’); }, 2000); }, []); return ( <UserContext.Provider value={userName}> <Layout> Main content </Layout> </UserContext.Provider> ); } // … |
Откройте демонстрацию, и вы увидите ‘John Smith’ (значение контекста), отображаемое на экране. Через 2 секунды значение контекста изменится на ‘Smith, John Smith’, и, соответственно, новое значение будет показано на экране.
Демонстрация показывает, что компонент потребитель <UserInfo />, отображает значение контекста на экране, при изменении значения контекста повторно отображает его значение.
function UserInfo() { const userName = useContext(UserContext); return <span>{userName}</span>; } |
4. Обновление контекста
React Context API по умолчанию не имеет состояния и не предоставляет специальный метод для обновления значения контекста из компонентов-потребителей.
Но это может быть легко реализовано путем интеграции механизма управления состоянием (подобно хукам useState() и useReducer()), и обеспечения функции обновления прямо в контексте рядом с самим значением. В следующем примере компонент <Application /> использует хук useState() для управления значением контекста.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
import { createContext, useState, useContext, useMemo } from ‘react’; const UserContext = createContext({ userName: », setUserName: () => {}, }); function Application() { const [userName, setUserName] = useState(‘John Smith’); const value = useMemo( () => ({ userName, setUserName }), [userName] ); return ( <UserContext.Provider value={value}> <UserNameInput /> </UserContext.Provider> ); } function UserNameInput() { const { userName, setUserName } = useContext(UserContext); const changeHandler = event => setUserName(event.target.value); return ( <input type=«text» value={userName} onChange={changeHandler} /> ); } function UserInfo() { const { userName } = useContext(UserContext); return <span>{userName}</span>; } |
Попробуйте демо.
Потребитель <UserNameInput /> читает значение контекста, откуда извлекаются userName и setUserName. Затем потребитель может обновить значение контекста, вызвав функцию обновления setUserName(newContextValue).
<UserInfo /> — еще один потребитель контекста. Когда обновляет контекст — обновляется и компонент <UserInfo />.
Обратите внимание, что <Application /> запоминает значение контекста. Объект значения контекста сохраняется неизменным до тех пор, пока userName остается неизменным, предотвращая повторную визуализацию потребителей каждый раз при повторной визуализации <Application />.
В противном случае во время повторного рендеринга будут созданы разные экземпляры объекта, что вызовет повторный рендеринг <Application /> в потребителях контекста.
5. Вывод
Контекст в React — это концепция, которая позволяет вам снабжать дочерние компоненты глобальными данными, независимо от того, насколько глубоко они находятся в дереве компонентов.
Для использования контекста требуется 3 шага: создание, предоставление и использование контекста. При интеграции контекста в ваше приложение учтите, что иногда проще передать свойство через 2–3 уровня иерархии чем использовать контекст, потому что он значительно усложняет работу.
Автор: Dmitri Pavlutin
Источник: dmitripavlutin.com
Редакция: Команда webformyself.
Читайте нас в Telegram, VK, Яндекс.Дзен
Хотите узнать, что необходимо для создания сайта?
Посмотрите видео и узнайте пошаговый план по созданию сайта с нуля!
Смотреть видео
ReactJS: основы
Изучите основы ReactJS и создайте ваше первое приложение на ReactJS
Смотреть
Перевод статьи
Контекст React предоставляет данные компонентам независимо от того, насколько глубоко они находятся в дереве компонентов.
В этом посте вы узнаете, как использовать концепцию контекста в React.
Оглавление
- Как использовать контекст
- Когда он вам пригодится?
- Пример использования (имя пользователя)
- Контекст на помощь
- Когда контекст меняется
- Обновление контекста
- Вывод
1 Как использовать контекст
Для использования контекста в React требуется 3 простых шага: создание контекста(A), предоставление контекста(Б) и использование контекста(В).
A.Создадим контекст
Заводская функция принимает один необязательный аргумент: ‘Любое значение’
Б. Предоставление контекста
Компонент Context.Provider — доступный в экземпляре контекста, используется для предоставления контекста его дочерним компонентам, независимо от их глубины.
Опять же, здесь важно то, что все компоненты, которые позже захотят использовать контекст, должны быть заключены в компонент провайдера.
В. Использование контекста
Использование контекста может быть выполнено двумя способами. Первый способ — это использовать хук useContext (Context)
Хук также гарантирует повторный рендеринг компонента при изменении значения контекста
Второй способ — использовать функцию рендеринга, предоставленную в качестве дочернего для специального компонента Context.Consumer, доступного в экземпляре контекста:
Вы можете иметь столько дочерних компонентов, сколько хотите для одного контекста. Если значение контекста изменяется (путем изменения свойства value провайдера (), то все дочерние компоненты немедленно уведомляются и повторно обрабатываются. Если дочерний компонент не заключен внутри провайдера, но все же пытается получить доступ к значению контекста (используя useContext (Context) или) , то значение контекста будет аргументом значения по умолчанию, предоставленным createContext (defaultValue) фабричная функция, создавшая контекст.
2 Когда он вам пригодится?
Основная идея использования контекста — предоставить вашим компонентам доступ к некоторым глобальным данным и повторный рендеринг при изменении этих глобальных данных. Контекст решает проблему сверления реквизита: когда вам нужно передать реквизиты от родителей детям.
Вы можете держать внутри контекста:
- Глобальное состояние приложения
- Тема приложения
- Конфигурация приложения
- Аутентификация пользователя
- Пользовательские настройки
- Предпочтительный язык
- Набор услуг
С другой стороны, вы должны хорошо подумать, прежде чем принимать решение об использовании контекста в своем приложении.
- Во-первых, интеграция контекста добавляет сложности. Создание контекста, обертывание всего в провайдере, использование useContext () в каждом дочернем компоненте — это увеличивает сложность.
- Во-вторых, добавление контекста затрудняет модульное тестирование компонентов. Во время модульного тестирования вам придется заключить потребительские компоненты в поставщик контекста. Включая компоненты, на которые косвенно влияет контекст — родителей дочерних компонентов контекста!
3 Пример использования (имя пользователя)
Самый простой способ передать данные от родителя к дочернему компоненту — это когда родитель назначает реквизиты своему дочернему компоненту
Родительский компонент назначает данные userName своему дочернему компоненту с помощью свойства userName.
Это обычный способ передачи данных с использованием реквизита. Вы можете использовать этот подход без проблем. Ситуация меняется, когда дочерний компонент не является прямым потомком , но содержится в нескольких предках. Например, предположим, что компонент (тот, у которого есть глобальные данные userName) отображает компонент , который, в свою очередь, отображает компонент
, который, в свою очередь, в конечном итоге отображает компонент (который хотел бы получить доступ к имени пользователя).
Посмотрим как это будет выглядеть
Вы можете видеть проблему: поскольку компонент отображается глубоко в дереве, и все родительские компоненты ( и
) должны передавать свойство userName.
Контекст React — возможное решение. Давайте посмотрим, как его применить в следующем разделе.
4 Контекст на помощь
Напоминаем, что для применения контекста React требуются 3 участника: контекст, провайдер, извлеченный из контекста, и дочерний компонент.
Вот как будет выглядеть пример приложения при применении к нему контекста
Во-первых, const UserContext = createContext (‘Unknown’) создает контекст, в котором будет храниться информация об имени пользователя.
Во-вторых, внутри компонента дочерние компоненты приложения заключены в контекст пользователя: . Обратите внимание, что свойство value компонента поставщика важно: именно так вы устанавливаете значение контекста.
Наконец, становится потребителем контекста с помощью встроенной ловушки useContext (UserContext). Хук вызывается с контекстом в качестве аргумента и возвращает значение имени пользователя.
Промежуточные компоненты и
не должны передавать свойство userName. В этом большое преимущество контекста — он снимает бремя передачи данных через промежуточные компоненты.
5 Когда контекст меняется
Когда значение контекста изменяется путем изменения свойства значения поставщика контекста (), тогда все его потребители уведомляются и повторно обрабатываются. Например, если я изменю имя пользователя с «Дэн Абрамов» на «Дэн, Дэн Абрамов», то потребитель немедленно выполнит повторный рендеринг, чтобы отобразить последнее значение контекста
6 Обновление контекста
React Context API по умолчанию не имеет состояния и не предоставляет специальный метод для обновления значения контекста из компонентов-потребителей.
Но это можно легко реализовать, интегрировав механизм управления состоянием (например, хуки useState () или useReducer ()) и предоставив функцию обновления прямо в контексте рядом с самим значением. В следующем примере компонент использует ловушку useState () для управления значением контекста.
потребитель считывает значение контекста, из которого извлекаются userName и setUserName. Затем потребитель может обновить значение контекста, вызвав функцию обновления setUserName (newContextValue)
— еще один потребитель контекста. Когда обновляет контекст, этот компонент также обновляется.
Обратите внимание, что запоминает значение контекста. Мемоизация сохраняет объект значения контекста одним и тем же до тех пор, пока userName остается неизменным, предотвращая повторную визуализацию потребителей каждый раз, когда повторно отображается . В противном случае, без мемоизации, const value = {userName, setUserName} создаст разные экземпляры объекта во время повторного рендеринга , инициируя повторный рендеринг в потребителях контекста.
7 Вывод
Контекст в React — это концепция, которая позволяет вам снабжать дочерние компоненты глобальными данными, независимо от того, насколько глубоко они находятся в дереве компонентов.
Для использования контекста требуется 3 шага: создание, предоставление и использование контекста. При интеграции контекста в ваше приложение учтите, что он значительно усложняет его. Иногда «прокинуть» пропсы через 2-3 уровня иерархии не составляет большой проблемы.
React context is an essential tool for every React developer to know. It lets you easily share state in your applications.
In this comprehensive guide, we will cover what React context is, how to use it, when and when not to use context, and lots more.
Even if you’ve never worked with React context before, you’re in the right place. You will learn everything you need to know with simple, step-by-step examples.
Let’s get started!
Table of Contents
- What is React context?
- When should you use React context?
- What problems does React context solve?
- How do I use React context?
- What is the useContext hook?
- You may not need context
- Does React context replace Redux?
- React context caveats
What is React context?
React context allows us to pass down and use (consume) data in whatever component we need in our React app without using props.
In other words, React context allows us to share data (state) across our components more easily.
When should you use React context?
React context is great when you are passing data that can be used in any component in your application.
These types of data include:
- Theme data (like dark or light mode)
- User data (the currently authenticated user)
- Location-specific data (like user language or locale)
Data should be placed on React context that does not need to be updated often.
Why? Because context was not made as an entire state management system. It was made to make consuming data easier.
You can think of React context as the equivalent of global variables for our React components.
What problems does React context solve?
React context helps us avoid the problem of props drilling.
Props drilling is a term to describe when you pass props down multiple levels to a nested component, through components that don’t need it.
Here is an example of props drilling. In this application, we have access to theme data that we want to pass as a prop to all of our app’s components.
As you can see, however, the direct children of App
, such as Header
, also have to pass the theme data down using props.
export default function App({ theme }) {
return (
<>
<Header theme={theme} />
<Main theme={theme} />
<Sidebar theme={theme} />
<Footer theme={theme} />
</>
);
}
function Header({ theme }) {
return (
<>
<User theme={theme} />
<Login theme={theme} />
<Menu theme={theme} />
</>
);
}
What is the issue with this example?
The issue is that we are drilling the theme
prop through multiple components that don’t immediately need it.
The Header
component doesn’t need theme
other than to pass it down to its child component. In other words, it would be better for User
, Login
and Menu
to consume the theme
data directly.
This is the benefit of React context – we can bypass using props entirely and therefore avoid the issue of props drilling.
How do I use React context?
Context is an API that is built into React, starting from React version 16.
This means that we can create and use context directly by importing React in any React project.
There are four steps to using React context:
- Create context using the
createContext
method. - Take your created context and wrap the context provider around your component tree.
- Put any value you like on your context provider using the
value
prop. - Read that value within any component by using the context consumer.
Does all this sound confusing? It’s simpler than you think.
Let’s take a look at a very basic example. In our App
, let’s pass down our own name using Context and read it in a nested component: User
.
import React from 'react';
export const UserContext = React.createContext();
export default function App() {
return (
<UserContext.Provider value="Reed">
<User />
</UserContext.Provider>
)
}
function User() {
return (
<UserContext.Consumer>
{value => <h1>{value}</h1>}
{/* prints: Reed */}
</UserContext.Consumer>
)
}
Let’s break down what we are doing, step-by-step:
- Above our
App
component, we are creating context withReact.createContext()
and putting the result in a variable,UserContext
. In almost every case, you will want to export it as we are doing here because your component will be in another file. Note that we can pass an initial value to ourvalue
prop when we callReact.createContext()
. - In our
App
component, we are usingUserContext
. SpecificallyUserContext.Provider
. The created context is an object with two properties:Provider
andConsumer
, both of which are components. To pass our value down to every component in our App, we wrap our Provider component around it (in this case,User
). - On
UserContext.Provider
, we put the value that we want to pass down our entire component tree. We set that equal to thevalue
prop to do so. In this case, it is our name (here, Reed). - In
User
, or wherever we want to consume (or use) what was provided on our context, we use the consumer component:UserContext.Consumer
. To use our passed down value, we use what is called the render props pattern. It is just a function that the consumer component gives us as a prop. And in the return of that function, we can return and usevalue
.
What is the useContext hook?
Looking at the example above, the render props pattern for consuming context may look a bit strange to you.
Another way of consuming context became available in React 16.8 with the arrival of React hooks. We can now consume context with the useContext hook.
Instead of using render props, we can pass the entire context object to React.useContext()
to consume context at the top of our component.
Here is the example above using the useContext hook:
import React from 'react';
export const UserContext = React.createContext();
export default function App() {
return (
<UserContext.Provider value="Reed">
<User />
</UserContext.Provider>
)
}
function User() {
const value = React.useContext(UserContext);
return <h1>{value}</h1>;
}
The benefit of the useContext hook is that it makes our components more concise and allows us to create our own custom hooks.
You can either use the consumer component directly or the useContext hook, depending on which pattern you prefer.
You may not need context
The mistake many developers make is reaching for context when once they have to pass props down several levels to a component.
Here is an application with a nested Avatar
component that requires two props username
and avatarSrc
from the App
component.
export default function App({ user }) {
const { username, avatarSrc } = user;
return (
<main>
<Navbar username={username} avatarSrc={avatarSrc} />
</main>
);
}
function Navbar({ username, avatarSrc }) {
return (
<nav>
<Avatar username={username} avatarSrc={avatarSrc} />
</nav>
);
}
function Avatar({ username, avatarSrc }) {
return <img src={avatarSrc} alt={username} />;
}
If possible, we want to avoid passing multiple props through components that don’t need it.
What can we do?
Instead of immediately resorting to context because we are prop drilling, we should better compose our components.
Since only the top most component, App
, needs to know about the Avatar
component, we can create it directly within App
.
This allows us to pass down a single prop, avatar
, instead of two.
export default function App({ user }) {
const { username, avatarSrc } = user;
const avatar = <img src={avatarSrc} alt={username} />;
return (
<main>
<Navbar avatar={avatar} />
</main>
);
}
function Navbar({ avatar }) {
return <nav>{avatar}</nav>;
}
In short: don’t reach for context right away. See if you can better organize your components to avoid prop drilling.
Does React context replace Redux?
Yes and no.
For many React beginners, Redux is a way of more easily passing around data. This is because Redux comes with React context itself.
However, if you are not also updating state, but merely passing it down your component tree, you do not need a global state management library like Redux.
React context caveats
Why it is not possible to update the value that React context passes down?
While it is possible to combine React context with a hook like useReducer and create a makeshift state management library without any third-party library, it is generally not recommended for performance reasons.
The issue with this approach lies in the way that React context triggers a re-render.
If you are passing down an object on your React context provider and any property on it updates, what happens? Any component which consumes that context will re-render.
This may not be a performance issue in smaller apps with few state values that are not updated very often (such as theme data). But it is a problem if you will be performing many state updates in an application with a lot of components in your component tree.
Conclusion
I hope this guide gave you a better understanding of how to use React context from front to back.
If you want an even more in-depth grasp of how to use React context to build amazing React projects, check out The React Bootcamp.
Want to become a React pro? Join The React Bootcamp
The React Bootcamp takes everything you should know about learning React and bundles it into one comprehensive package, including videos, cheatsheets, plus special bonuses.
Gain the insider information 100s of developers have already used to become a React pro, find their dream job, and take control of their future:
Click here to be notified when it opens
Learn to code for free. freeCodeCamp’s open source curriculum has helped more than 40,000 people get jobs as developers. Get started
React context is an essential tool for every React developer to know. It lets you easily share state in your applications.
In this comprehensive guide, we will cover what React context is, how to use it, when and when not to use context, and lots more.
Even if you’ve never worked with React context before, you’re in the right place. You will learn everything you need to know with simple, step-by-step examples.
Let’s get started!
Table of Contents
- What is React context?
- When should you use React context?
- What problems does React context solve?
- How do I use React context?
- What is the useContext hook?
- You may not need context
- Does React context replace Redux?
- React context caveats
What is React context?
React context allows us to pass down and use (consume) data in whatever component we need in our React app without using props.
In other words, React context allows us to share data (state) across our components more easily.
When should you use React context?
React context is great when you are passing data that can be used in any component in your application.
These types of data include:
- Theme data (like dark or light mode)
- User data (the currently authenticated user)
- Location-specific data (like user language or locale)
Data should be placed on React context that does not need to be updated often.
Why? Because context was not made as an entire state management system. It was made to make consuming data easier.
You can think of React context as the equivalent of global variables for our React components.
What problems does React context solve?
React context helps us avoid the problem of props drilling.
Props drilling is a term to describe when you pass props down multiple levels to a nested component, through components that don’t need it.
Here is an example of props drilling. In this application, we have access to theme data that we want to pass as a prop to all of our app’s components.
As you can see, however, the direct children of App
, such as Header
, also have to pass the theme data down using props.
export default function App({ theme }) {
return (
<>
<Header theme={theme} />
<Main theme={theme} />
<Sidebar theme={theme} />
<Footer theme={theme} />
</>
);
}
function Header({ theme }) {
return (
<>
<User theme={theme} />
<Login theme={theme} />
<Menu theme={theme} />
</>
);
}
What is the issue with this example?
The issue is that we are drilling the theme
prop through multiple components that don’t immediately need it.
The Header
component doesn’t need theme
other than to pass it down to its child component. In other words, it would be better for User
, Login
and Menu
to consume the theme
data directly.
This is the benefit of React context – we can bypass using props entirely and therefore avoid the issue of props drilling.
How do I use React context?
Context is an API that is built into React, starting from React version 16.
This means that we can create and use context directly by importing React in any React project.
There are four steps to using React context:
- Create context using the
createContext
method. - Take your created context and wrap the context provider around your component tree.
- Put any value you like on your context provider using the
value
prop. - Read that value within any component by using the context consumer.
Does all this sound confusing? It’s simpler than you think.
Let’s take a look at a very basic example. In our App
, let’s pass down our own name using Context and read it in a nested component: User
.
import React from 'react';
export const UserContext = React.createContext();
export default function App() {
return (
<UserContext.Provider value="Reed">
<User />
</UserContext.Provider>
)
}
function User() {
return (
<UserContext.Consumer>
{value => <h1>{value}</h1>}
{/* prints: Reed */}
</UserContext.Consumer>
)
}
Let’s break down what we are doing, step-by-step:
- Above our
App
component, we are creating context withReact.createContext()
and putting the result in a variable,UserContext
. In almost every case, you will want to export it as we are doing here because your component will be in another file. Note that we can pass an initial value to ourvalue
prop when we callReact.createContext()
. - In our
App
component, we are usingUserContext
. SpecificallyUserContext.Provider
. The created context is an object with two properties:Provider
andConsumer
, both of which are components. To pass our value down to every component in our App, we wrap our Provider component around it (in this case,User
). - On
UserContext.Provider
, we put the value that we want to pass down our entire component tree. We set that equal to thevalue
prop to do so. In this case, it is our name (here, Reed). - In
User
, or wherever we want to consume (or use) what was provided on our context, we use the consumer component:UserContext.Consumer
. To use our passed down value, we use what is called the render props pattern. It is just a function that the consumer component gives us as a prop. And in the return of that function, we can return and usevalue
.
What is the useContext hook?
Looking at the example above, the render props pattern for consuming context may look a bit strange to you.
Another way of consuming context became available in React 16.8 with the arrival of React hooks. We can now consume context with the useContext hook.
Instead of using render props, we can pass the entire context object to React.useContext()
to consume context at the top of our component.
Here is the example above using the useContext hook:
import React from 'react';
export const UserContext = React.createContext();
export default function App() {
return (
<UserContext.Provider value="Reed">
<User />
</UserContext.Provider>
)
}
function User() {
const value = React.useContext(UserContext);
return <h1>{value}</h1>;
}
The benefit of the useContext hook is that it makes our components more concise and allows us to create our own custom hooks.
You can either use the consumer component directly or the useContext hook, depending on which pattern you prefer.
You may not need context
The mistake many developers make is reaching for context when once they have to pass props down several levels to a component.
Here is an application with a nested Avatar
component that requires two props username
and avatarSrc
from the App
component.
export default function App({ user }) {
const { username, avatarSrc } = user;
return (
<main>
<Navbar username={username} avatarSrc={avatarSrc} />
</main>
);
}
function Navbar({ username, avatarSrc }) {
return (
<nav>
<Avatar username={username} avatarSrc={avatarSrc} />
</nav>
);
}
function Avatar({ username, avatarSrc }) {
return <img src={avatarSrc} alt={username} />;
}
If possible, we want to avoid passing multiple props through components that don’t need it.
What can we do?
Instead of immediately resorting to context because we are prop drilling, we should better compose our components.
Since only the top most component, App
, needs to know about the Avatar
component, we can create it directly within App
.
This allows us to pass down a single prop, avatar
, instead of two.
export default function App({ user }) {
const { username, avatarSrc } = user;
const avatar = <img src={avatarSrc} alt={username} />;
return (
<main>
<Navbar avatar={avatar} />
</main>
);
}
function Navbar({ avatar }) {
return <nav>{avatar}</nav>;
}
In short: don’t reach for context right away. See if you can better organize your components to avoid prop drilling.
Does React context replace Redux?
Yes and no.
For many React beginners, Redux is a way of more easily passing around data. This is because Redux comes with React context itself.
However, if you are not also updating state, but merely passing it down your component tree, you do not need a global state management library like Redux.
React context caveats
Why it is not possible to update the value that React context passes down?
While it is possible to combine React context with a hook like useReducer and create a makeshift state management library without any third-party library, it is generally not recommended for performance reasons.
The issue with this approach lies in the way that React context triggers a re-render.
If you are passing down an object on your React context provider and any property on it updates, what happens? Any component which consumes that context will re-render.
This may not be a performance issue in smaller apps with few state values that are not updated very often (such as theme data). But it is a problem if you will be performing many state updates in an application with a lot of components in your component tree.
Conclusion
I hope this guide gave you a better understanding of how to use React context from front to back.
If you want an even more in-depth grasp of how to use React context to build amazing React projects, check out The React Bootcamp.
Want to become a React pro? Join The React Bootcamp
The React Bootcamp takes everything you should know about learning React and bundles it into one comprehensive package, including videos, cheatsheets, plus special bonuses.
Gain the insider information 100s of developers have already used to become a React pro, find their dream job, and take control of their future:
Click here to be notified when it opens
Learn to code for free. freeCodeCamp’s open source curriculum has helped more than 40,000 people get jobs as developers. Get started