How to use React Context

Published

React Context is a powerful feature introduced in React v16.3.0. It allows you to access values from the current context (e.g. the parent) inside your components' render methods, where you normally would be limited to accessing only its local state and props . This creates a very flexible system for sharing values between components. In this article, we'll cover how to use React Context to create a shared state, and how to consume that state in a child component.

This article assumes you are familiar with the basics of React, such as JSX, components, state, and props . If you're not familiar with these concepts yet, I would recommend reading the official React docs first.

What is React Context?

React Context is a new feature that allows you to share data between components. It's similar in concept to React's useState() hook, but it allows you to share data across multiple components at once. The most common use case for using the context API is sharing state across one or more child components. Let's look at an example:

jsx
import React from 'react'
const ParentComponent = () => {
const [counter, setCounter] = React.useState(0)
return (
<ChildComponent
counter={counter}
onIncrement={() => setCounter(counter + 1)}
/>
)
}
const ChildComponent = ({ counter, onIncrement }) => {
return (
<div>
{counter}
<button onClick={onIncrement}>increment</button>
</div>
)
}

Here, we have a <ParentComponent> that sets its own local state with an initial value of 0. The component is rendering a <ChildComponent />, which is receiving the current counter state as prop. It also updates the parent's counter state when the increment button is clicked.

This works great for simple cases, but it can quickly get out of hand if we want to share more state between components. Components start to become more and more coupled, which makes them harder to maintain and reason about. We can do better than this!

Instead of passing the counter prop into ChildComponent, we can use the React Context API to share the counter state between both components. Here's how:

jsx
import React from 'react'
const CounterContext = React.createContext(0)
const ParentComponent = () => {
const [counter, setCounter] = React.useState(0)
const increment = () => setCounter(counter + 1)
return (
<CounterContext.Provider value={{ counter, increment }}>
<ChildComponent />
</CounterContext.Provider>
)
}
const ChildComponent = () => {
const { counter, increment } = React.useContext(CounterContext)
return (
<div>
{counter}
<button onClick={() => increment()}>increment</button>
</div>
)
}

The ChildComponent is now consuming the counter state and increment action from the context instead of receiving them as props . This allows us to avoid coupling ChildComponent to ParentComponent.

How is this different from useState()?

The biggest difference between the context API and useState() is that the context API allows you to share state between multiple components at once. The useState() hook is scoped to a single component only.

Another difference is that the context API can be used to share any type of data, not just state. Like functions for example. We can even use context to share an entire Redux store (but don't do that please).

How does the context API work?

The Context object is a special type of React object that can be used to create and share data. It has two component: Provider and Consumer. The provider is used to create new context objects, while the consumer is used to access values from those contexts.

The context API allows you to provide multiple consumers with a single provider. For example:

jsx
import React from 'react'
const CounterContext = React.createContext(0)
const ParentComponent = () => {
const [counter, setCounter] = React.useState(0)
const increment = () => setCounter(counter + 1)
return (
<CounterContext.Provider value={{ counter, increment }}>
<DisplayCounterComponent />
<IncrementCounterComponent />
</CounterContext.Provider>
)
}
const DisplayCounterComponent = () => {
const { counter } = React.useContext(CounterContext)
return <div>{counter}</div>
}
const IncrementCounterComponent = () => {
const { increment } = React.useContext(CounterContext)
return (
<div>
<button onClick={() => increment()}>increment</button>
</div>
)
}

Here, we are using the CounterContext provider to share the context between multiple components. The DisplayCounterComponent is retrieving the counter state and displays it value, while IncrementCounterComponent is retrieving the increment action from the context instead.

Create a custom context provider

In the previous examples, we were using the CounterContext.Provider directly. But we can create our own CounterProvider component that will make it easier to reuse the context logic.

jsx
import React from 'react'
const CounterContext = React.createContext(0)
const CounterProvider = ({ children, initialCount = 0 }) => {
const [counter, setCounter] = React.useState(initialCount)
const increment = () => setCounter(counter + 1)
return (
<CounterContext.Provider value={{ counter, increment }}>
{children}
</CounterContext.Provider>
)
}

The CounterProvider component receives an initial count from the parent, which it uses to create the context's initial state. It also receives a children prop, which we are passing through so any child component will be rendered.

Now let's see how we can use CounterProvider in our app.

jsx
import React from 'react'
const ParentComponent = () => {
return (
<CounterProvider initialCount={0}>
<ChildComponent />
</CounterProvider>
)
}
const ChildComponent = () => {
const { counter, increment } = React.useContext(CounterContext)
return (
<div>
{counter}
<button onClick={() => increment()}>increment</button>
</div>
)
}

The useContext hook is still used to retrieve our counter context within any child component. But we can create a custom useCounterContext hook for this for better reusability.

jsx
const useCounterContext = () => React.useContext(CounterContext)

Now we can use useCounterContext instead of useContext inside our components.

jsx
const ChildComponent = () => {
const { counter, increment } = useCounterContext()
return (
<div>
{counter}
<button onClick={() => increment()}>increment</button>
</div>
)
}

Closing thoughts

The context API is a powerful tool for creating shared state between multiple components. It's a great way of avoiding coupling and your components together, which makes them easier to test and maintain.

The React docs have a great article on the context API as well. It's worth taking a look at if you are interested in learning more.