I have to admit, I’ve never fully taken the time to learn React Context API. Sure I’ve “used” it, but it’s always been me skirting around the edges of existing code.

This post puts an end to that. I’ll walk through the basics of what I’ve learnt and how to put it into practice in your day to day React work.

What is the Context API?

The official React docs do a great job of explaining the purpose of Context:

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

https://reactjs.org/docs/context.html

So with Context we can pass data down through a set of nested components without having to resort to “prop drilling“. Nice!

When should you make use of Context?

Context is best suited to data which is considered to be shared by a tree of components.

An example of this can be seen within the WordPress Gutenberg editor codebase, where the new suite of “Query” blocks utilise a shared Context to provide data about the current query parameters across the suite of Blocks.

Another use case, presented by the official React docs, is passing a “current Theme” value down through an App.

How to use the Context API

The first step is to create a Context object by calling createContext() passing an optional default value:

const someContext = React.createContext(null)

Every Context object comes with a <Provider> React component which we use to wrap the components to which we want the context to be available:

import TestContext from "./TestContext";

<TestContext.Provider value="Hello from context">
    <Welcome />
</TestContext.Provider>

We pass a value prop to our <Provider> which will be the value of the Context to be made available to the consuming components.

With this in place, all the React components below the <Provider> can “subscribe” to the Context using the useContext hook.

import TestContext from "./TestContext";

function Title() {
  const msg = useContext(TestContext);
  return <h1>The title is: {msg}</h1>;
}

What values can be stored in context?

Context can store any valid JavaScript value including objects and functions.

Indeed, it’s simple to pass both the value and the “setter” function from a useState call as a Context’s value and allow nested components to both read and trigger updates to that state.

Performance Gotchas

It’s worth remembering however, that every time a Context’s value changes, all the subscribing children of the <Provider> are re-rendered.

As “changes” to value are determined by referential equality, it’s best to avoid re-creating objects and passing them as the value prop.

Be careful, as this is easily done. Consider the example below:

// Don't do this!
<TestContext.Provider value={ { recreated: 'eachtime!' } }>
    <Welcome />
</TestContext.Provider>

Here the Context value will change each time because a new object reference is created “inline” on each render:

Instead, prefer hoisting objects (including functions) out and using memoization to avoid unnecessary re-renders:

const providerValue = useMemo(() => { recreated: 'eachtime!' });

<TestContext.Provider value={ providerValue }>
    <Welcome />
</TestContext.Provider>

In the example above, the useMemo hook ensures the same object reference is provided to the Context on each render thereby avoiding unnecessary re-rendering of the components that are consuming the Context.

Advanced Patterns

One benefit of Context is to avoid having to pass down callback props through a nested tree of components.

However, as discussed above passing a callback function directly as the Context’s value would cause the Context’s consumers to re-render as it would fail the reference equality check.

One way to avoid this is to utilise the useCallback hook to wrap the function. However, this is a little verbose and, as with many optimisations, it is easy to forget or miss.

A neat trick to avoid this is to make use of the dispatch function provided by the useReducer hook, passing this down as value:

const [msg, dispatch] = useReducer(msgReducer);

<TestContext.Provider value={ dispatch }>
    <Welcome />
</TestContext.Provider>

As outlined in the official React docs, the dispatch context never changes, so components that consume it will not re-render.

The downside, is that if you also need to pass the current state value (in the example above this would be msg) then you’d need to use a separate context.

According to the React docs, this pattern is the recommended way for deep updates.

Conclusion

So there you have it. React Context in a nutshell. I hope you’ve found this useful? If you’d like to learn more you should read the following useful resources:

2 responses to “Learning the React Context API

  1. Damian says:

    Very nice article, Dave. 👏
    I’ve been working on some stuff that requires pass data through some components. Since this API demands tree hierarchy in order to propagate the data, I wondered whether a portal could affect the data flowing.
    Do you have an idea about this?

    1. Dave Smith says:

      Glad you liked it Damián!

      According to the official React docs

      > Even though a portal can be anywhere in the DOM tree, it behaves like a normal React child in every other way. Features like context work exactly the same regardless of whether the child is a portal, as the portal still exists in the React tree regardless of position in the DOM tree.

      So I’d say that it doesn’t require a DOM hierarchy in order to propagate data. Rather it relies on the React component hierarchy.

      I hope that helps?

Leave a Reply to Damian Cancel reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.