Working with the new React Context API

new-react-context-api-header.png

Discover the new React Context API. See how it compares to the old version, and learn how to use it to share values within a React application.

Introduction

You must have been hearing about the new React 16.3 buzz all around social media and developer newsletters. There are a few interesting new features that explains the reason for the excitement. One of them is the new Context API.

This post explains the React Context API by showing off the old and new version, comparing them, and explaining how best to use the API.

Why is there a Context API

Let’s start with understanding why a Context API actually exists. To do so, we need go back to basics and review how components work and how their composition affects the way we build apps.

The following image represents an app composed of different components with some data flowing through:

React hierarchy

The <Nav /> component is the child of <Dashboard />, which is also the child of <App />. Therefore, <Nav /> is a grandchild to <App />. The <Nav /> component needs an authenticated user (authUser)payload to render to the view and the only component that has this data is the <``App /> component.

To get this data from <App /> to <Nav />, we need to first pass the data through <Dashboard />, which in turn passes it down to <Nav />. This might seem simple but imagine for a second what it’s going to look like when you have not just <Dashboard /> but about 3-5 or more components between <App /> and <Nav />. You would need to pass down the data down to each of the components just to get to the bottom.

Here is a code example that recreates the problem:

1import React from "react";
2    import { render } from "react-dom";
3
4    const Nav = ({ authUser }) => (
5      <div>
6        <p>
7          Your username is <strong>{authUser.username}</strong>
8        </p>
9      </div>
10    );
11
12    const Dashboard = ({ authUser }) => (
13      <div>
14        <h3>Yo! This is the dashboard</h3>
15        <Nav authUser={authUser} />
16      </div>
17    );
18
19    const authUser = {
20      username: "codebeast"
21    };
22    const App = () => (
23      <div>
24        <Dashboard authUser={authUser} />
25      </div>
26    );
27
28    render(<App />, document.getElementById("root"));

You can also view the Codesandbox demo to see it in action:

Libraries like Redux are meant to mitigate these kinds of challenges. Nevertheless, if you have ever used a state management library like Redux, you would know that it’s near to worthless having to go through those hurdles just to pass down something like an authenticated user. This is where context comes in.

React hierarchy with Context

With the Context API, you can bypass <Dashboard /> and get the data straight down to <Nav /> from the <App /> component level.

The old React Context API

React 16.3 is not the first version to introduce the Context API. It’s been around for ages but was labelled unstable by the React team which scared people away from using it. Seems like our fears got confirmed after the new release.

The old (and of course new) Context API lets you bypass intermediary components so that deep rooted components can get data from top level components.

Take a look at the following example:

1//...
2    import PropTypes from "prop-types";
3
4    const Nav = ({}, { authUser }) => (
5      <div>
6        <p>
7          Your username is <strong>{authUser.username}</strong>
8        </p>
9      </div>
10    );
11
12    Nav.contextTypes = {
13      authUser: PropTypes.object
14    };

The grandchild component defines a static contextTypes which creates a contract for the context. Instead of getting the authUser from the props, we are now extracting it from the context object which is passed as the second argument to functional components or can be accessed from class components through this.context.

Next, we need to make App provide authUser in the context:

1class App extends React.Component {
2      static childContextTypes = {
3        authUser: PropTypes.object
4      };
5      state = {
6        authUser: {
7          username: "codebeast"
8        }
9      };
10      getChildContext() {
11        return { authUser: this.state.authUser };
12      }
13      render() {
14        return (
15          <div>
16            <Dashboard />
17          </div>
18        );
19      }
20    }

The top level component defines a getChildContext method which returns whatever context we need to pass down the component tree. You must also define a childContextTypes to match the context contract that was defined in the grandchild component.

Notice how the dashboard component is rendered without passing any props to it?
Here is the complete old Context API example live:

The challenges with the old Context API

The main challenge with this API is with the shouldComponentUpdate lifecycle method. This component is used to disable updates in a component when the change made does not require the component to re-render.

shouldComponentUpdate is a good feature to have in React since you can use it to bail out of unnecessary component re-renders and re-calculations. Hence, improving the overall performance of your app.

The problem however, is that when a component like <Dashboard />, which is an intermediary component, bails out and does not re-render, React will reuse the entire subtree. In that case, updates won’t be pushed down to <Nav /> because <Dashboard /> bailed.

The new Context API solves this problem.

Extending PureComponent instead of Component is same as calling shouldComponentUpdate so it will have the same effect on the old Context API.

The new Context API

The new Context API does not rely on passing a context down the component tree. Rather, it uses a provider-consumer pair API to communicate between distant components in the hierarchy. The top level component provides the data and all other components in the hierarchy consume the data.

Here is a re-write of the previous example to use the new API:

1const AuthContext = React.createContext({
2      username: ""
3    });

First thing to do is to create a context using the createContext method. The method takes a default value as argument which it uses if the Provider fails to provide a value. The method returns an object that contains the Provider and Consumer.

You can use the Provider in <App /> to produce a value:

1const authUser = {
2      username: "codebeast"
3    };
4
5    const App = () => (
6      <div style={styles}>
7        <AuthContext.Provider value={authUser}>
8          <Dashboard />
9        </AuthContext.Provider>
10      </div>
11    );

The Provider wraps the child component and takes a value prop which it sends down the tree. The content of value can be a state object.

Next, wrap the <Nav /> root element with the consumer:

1const Nav = () => (
2      <AuthContext.Consumer>
3        {authUser => (
4          <div>
5            <p>
6              Your username is <strong>{authUser.username}</strong>
7            </p>
8          </div>
9        )}
10      </AuthContext.Consumer>
11    );

The Consumer takes a render prop function that receives the value (authUser) from the provider through its parameter. You can then render the value.

The <Dashboard /> now stays out of the data transfer business and has no prop about authUser flowing through it:

1const Dashboard = () => (
2      <div>
3        <h3>Yo! This is the dashboard</h3>
4        <Nav />
5      </div>
6    );

Here is another live example showing the new API:

Updating context

Updating context works same way as updating props. Most times, the source of data going in as context is from the state. Therefore, when a state change is triggered, the value of context will also change. It just works!

Conclusion

The old and new APIs still live in React 16.3 so you shouldn’t worry about breaking changes. On the other hand, I would advise you start using the new one as early as possible and also migrate old to new as soon as you can. The old API will be deprecated in a future release. It’s been around for ages. You can learn more about the new API and some usage examples in the React docs.