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:
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:
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:
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 { increment } = 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.
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.
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.
const useCounterContext = () => React.useContext(CounterContext)
Now we can use useCounterContext
instead of useContext
inside our components.
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.
Top comments (0)