DEV Community

Cover image for How to use React context
Robin van der Vleuten
Robin van der Vleuten

Posted on • Originally published at robinvdvleuten.nl

How to use React context

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>
    )
}
Enter fullscreen mode Exit fullscreen mode

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>
    )
}
Enter fullscreen mode Exit fullscreen mode

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>
    )
}
Enter fullscreen mode Exit fullscreen mode

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>
    )
}
Enter fullscreen mode Exit fullscreen mode

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>
    )
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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>
    )
}
Enter fullscreen mode Exit fullscreen mode

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)