Quick Links

React contexts are a feature which help you eliminate repetitive prop drilling. Props are often passed down from a parent component to a deeply nested child. This requires each intermediary component to "forward" the prop to the next component in the tree.

Contexts let you pass props through the tree without manually forwarding them at each level. This is particularly useful for data representing top-level application state. User settings, authentication tokens and data cached from API responses are good candidates for the Context API.

This data is often managed by a dedicated state store such as Redux. React's contexts can often be used instead. This removes a significant amount of complexity within apps where Redux is only used to hold application-level state. Instead of creating an action, dispatcher and reducer, you can use a React context.

An Example Without Context

After a user logs in, you'll often want to store essential data such as their name and email address in your app's state. This allows each component to display information about the user without making a round-trip to your API server.

Here's a naive way of implementing this:

const UserProfileLink = ({user}) => {
    

return (

<div>

<p>Logged in as {user.Name}</p>

<a href="/logout">Logout</a>

</div>

);

};

const Header = ({user}) => {

return (

<div>

<h1>My App</h1>

<UserProfileLink user={user} />

</div>

);

}

const App = () => {

const [user, setUser] = useState({

Name: "Foo Bar",

Email: "foobar@example.com"

});

return <Header user={user} />

}

There are three components in the app. The top-level

        App
    

component keeps track of the current user within its state. It renders the

        Header
    

component.

        Header
    

renders

        UserProfileLink
    

, which displays the user's name.

Crucially, only

        UserProfileLink
    

actually interacts with the user object.

        Header
    

still has to receive a

        user
    

prop though. This is then forwarded straight into

        UserProfileLink
    

, without being consumed by

        Header
    

.

The problems with this approach are exasperated as your component tree becomes more complex. You might forward props through multiple nested levels, even though the intermediary components don't use the props themselves.

Adding the Contexts API

You can mitigate these issues using the Contexts API. We can refactor the example above to remove the

        user
    

prop from

        Header
    

.

The

        App
    

component will create a new context to store the current user object.

        UserProfileLink
    

will consume the context. The component will be able to access the user data provided by the context. This is a similar concept to connecting a component to a Redux store.

const UserContext = React.createContext(null);
    

const UserProfileLink = () => {

const user = useContext(UserContext);

return (

<div>

<p>Logged in as {user.Name}</p>

<a href="/logout">Logout</a>

</div>

);

};

const Header = () => {

return (

<div>

<h1>My App</h1>

<UserProfileLink />

</div>

);

}

const App = () => {

const [user, setUser] = useState({

Name: "Foo Bar",

Email: "foobar@example.com"

});

return (

<UserContext.Provider value={user}>

<Header />

</UserContext>

);

}

This refactored set of components illustrates how to use Contexts.

The process starts by creating a new context for the value you'd like to make available. The call to

        React.createContext(null)
    

sets up a new context with a default value of

        null
    

.

The

        App
    

component has been rewritten to wrap

        Header
    

in

        UserContext.Provider
    

. The

        value
    

prop determines the current value of the context. This is set to the

        user
    

object held in state. Any components nested below the context provider can now consume the context and access the user object.

If you try to access the context from a component that's not nested within a provider instance, the component will receive the default value you passed to

        createContext()
    

. This should generally be avoided except for static context values which will never change.

Access to the provided context value is observed in

        UserProfileLink
    

. The

        user
    

prop has been removed. The component uses React's useContext() hook to retrieve the current value of the

        UserContext
    

. This will provide the user object injected by the

        App
    

component!

The final change is to the

        Header
    

component. This no longer needs to forward the

        user
    

prop so it can be removed entirely. In fact, the

        user
    

prop is gone from the entire app. It's now fed by

        App
    

into the

        UserContext
    

provider, not any specific component.

Using Context With Class Components

So far we've only used contexts within functional components. Contexts work well here as the

        useContext()
    

hook simplifies accessing the provided data within child components.

You can also use contexts with class components. The preferred way is to set the static

        contextType
    

class property to the context instance you want to use. React will read this property and set the

        context
    

property on component instances to the current value provided by the context.

const UserContext = React.createContext({Name: "Foo Bar"});
    

class MyComponent extends React.Component {

// Tell React to inject the `UserContext` value

static contextType = UserContext;

render() {

// Requested context value made available as `this.context`

return <p>{this.context.Name}</p>;

}

}

An alternative approach is to render your component's children within a context "consumer". You can access each context's consumer as the

        Consumer
    

property of the context instance.

You must provide a function as the consumer's child. The function will be called with the context's value when the component renders.

Here's what this looks like:

const UserContext = React.createContext({Name: "Foo Bar"});
    

class MyComponent extends React.Component {

render() {

return (

<UserContext.Consumer>

{user => <p>{user.Name}</p>}

</UserContext.Consumer>

);

}

}

Using

        contextType
    

restricts you to one context per component. Context consumers address this issue but can make your component's render method more opaque. Neither approach is quite as straightforward as the

        useContext()
    

hook available to functional components.

Updating Context Values

Context values function similarly to props. If a child needs to update context values, add a function to the context. The child could call the function to effect the context value change.

const UserContext = React.createContext(null);
    

const UserProfileLink = () => {

const context = useContext(UserContext);

return (

<div>

<p>Logged in as {context.user.Name}</p>

<a onClick={() => context.logoutUser()}>Logout</a>

</div>

);

};

const Header = () => {

return (

<div>

<h1>My App</h1>

<UserProfileLink />

</div>

);

}

const App = () => {

const [user, setUser] = useState({

Name: "Foo Bar",

Email: "foobar@example.com"

});

const contextValue = {

user,

logoutUser: () => setUser(null)

}

return (

<UserContext.Provider value={contextValue}>

<Header />

</UserContext>

);

}

This revised example shows how

        App
    

now provides a

        logoutUser
    

function within the context. Context consumers can call this function to update the

        user
    

in the

        App
    

component's state, causing the context's value to be modified accordingly.

Managing Re-Renders

React will re-render all children of a context provider whenever the provider's

        value
    

prop changes. Value changes are compared using

        Object.is()
    

, which means you must take care when using objects as the context provider's

        value
    

.

const App = () => {
    

return (

<ExampleContext.Provider value={{foo: "bar"}}>

<NestedComponent />

</ExampleContext.Provider>

);

}

This component would re-render

        NestedComponent
    

every time

        App
    

renders. A new object instance is created for the provider's

        value
    

prop each time, so React re-renders the component's children.

You can address this by lifting the

        value
    

object into the component's state. This will ensure the same object is rendered each time:

const App = () => {
    

const [obj, setObj] = useState({foo: "bar"});

return (

<ExampleContext.Provider value={obj}>

<NestedComponent />

</ExampleContext.Provider>

);

}

Summary

React Contexts help you cut out prop drilling by providing a first-class way to pass data down the component tree. Contexts are a good alternative to state libraries like Redux. They're built-in to React and are gaining traction among projects where Redux is used solely to connect deeply nested components to a shared source of state.

Contexts are intended for "global" data held within your application. They may also hold data for a specific sub-section of your app. Contexts aren't meant to replace local component state entirely. Individual components should continue to use state and props where the data's only going to pass up and down a shallowly nested tree.