Navigate back to the homepage
📚 Books

The old vs new React Context API

Mark Pollmann
June 15th, 2018 · 2 min read

With React 16.3 a new Context API will be introduced, making the old, experimental Context API obsolete. This marks a good time to take a look at the old and the new, what changes and why context is even needed in the first place.

What is Context?

I’m sure you’ve had the problem of a component needing some data from way up the component tree. Without using a state-management library like Redux the only solution seems to be prop drilling,passing the data down each component in-between the data provider and the data receiver, which looks something like this:

1<Provider {...props}>
2 <ComponentI {...props}>
3 <ComponentDont {...props}>
4 <ComponentNeed {...props}>
5 <ComponentThis {...props}>
6 <Receiver {...props}>

This sucks.

React provided a experimental way to solve this problem with the context API. Let’s see how it works.

The old context API

The idea is for the Provider to define a plain object from which the Receiver can select properties they are interested in.

Provider API

Let’s say we have a color that we want to pass down. First we define a getChildContext() function that returns the plain object containing the data we want to pass down.
Then we define childContextTypes, like we would define prop types. This is what it looks like:

1class MyProvider extends React.Component {
2 getChildContext() {
3 return { color: "green" }
4 }
5
6 render() {
7 return /* ... */
8 }
9}
10
11MyProvider.childContextTypes = {
12 color: PropTypes.string,
13}

Receiver API

The Receiver selects which of the properties of the context object he wants with the contextTypes property, analogously to the childContextTypes property of the Provider. Then these properties are made available on the class’ context:

1class MyReceiver extends React.Component {
2 render() {
3 const { color } = this.context
4 return `The color is ${color}`
5 }
6}
7
8MyReceiver.contextTypes = {
9 color: PropTypes.string,
10}

Usage examples

Even though the React team was quite clear that it’s not a stable API, many libaries made use of it. Two quick examples:

react-router

React-Router makes the router property available in its context. See this short code snippet from the react-router source code:

1class Router extends React.Component {
2 static propTypes = {
3 history: PropTypes.object.isRequired,
4 children: PropTypes.node
5 };
6
7 static contextTypes = {
8 router: PropTypes.object
9 };
10
11 static childContextTypes = {
12 router: PropTypes.object.isRequired
13 };
14
15 getChildContext() {
16 return {
17 router: {
18 ...this.context.router,
19 history: this.props.history,
20 route: {
21 location: this.props.history.location,
22 match: this.state.match
23 }
24 }
25 };
26 }
27 /* ... */

react-redux

React-redux makes the redux store available to react components. For this it uses the Provider component as the Parent on the one hand as well as the connect function that wraps components in a Higher-Order Component with the redux state made available to Child components. See these code snippets from the react-redux library source code:

inside Provider.js:

1export function createProvider(storeKey = "store", subKey) {
2 const subscriptionKey = subKey || `${storeKey}Subscription`
3
4 class Provider extends Component {
5 getChildContext() {
6 return { [storeKey]: this[storeKey], [subscriptionKey]: null }
7 }
8
9 /* ... */
10
11 render() {
12 return Children.only(this.props.children)
13 }
14 }
15
16 /* ... */
17
18 Provider.propTypes = {
19 store: storeShape.isRequired,
20 children: PropTypes.element.isRequired,
21 }
22 Provider.childContextTypes = {
23 [storeKey]: storeShape.isRequired,
24 [subscriptionKey]: subscriptionShape,
25 }
26
27 return Provider
28}

inside connectAdvanced.js:

1/* ... */
2 const contextTypes = {
3 [storeKey]: storeShape,
4 [subscriptionKey]: subscriptionShape,
5 }
6 const childContextTypes = {
7 [subscriptionKey]: subscriptionShape,
8 }
9/* ... */
10 getChildContext() {
11 const subscription = this.propsMode ? null : this.subscription
12 return { [subscriptionKey]: subscription || this.context[subscriptionKey] }
13 }
14/* ... */

The new API

Things are going to be quite a bit simpler. Let’s look at an example:

An Example

1const BananaContext = React.createContext("hello")
2
3class App extends React.Component {
4 render() {
5 return (
6 <BananaContext.Provider value="hola">
7 <BananaContext.Consumer>
8 {context => <div>{context}</div>}
9 </BananaContext.Consumer>
10 </BananaContext.Provider>
11 )
12 }
13}
14// Prints hola

Let’s look at the individual parts.

React.createContext()

React.createContext() creates an object that exposes two objects: A Provider and a Consumer. It takes a default argument that will get used if the Consumer is not wrapped in a corresponding Provider. Like this:

1const BananaContext = React.createContext("hello")
2
3class App extends React.Component {
4 render() {
5 return (
6 <BananaContext.Consumer>
7 {context => <div>{context}</div>}
8 </BananaContext.Consumer>
9 )
10 }
11}
12// Prints hello

The default argument can of course also be an object if you need to provide more than one value.

Provider

The Provider provides the value in its appropriately name value prop. A more interesting value would be something from its internal state like so: value={this.state.someValue}.

Consumer

The Consumer takes a function as a child with the context provided as its only parameter. The function as a child pattern makes it flexible in the way how to process the given context. I’ll definitely write a post about this pattern since it’s pretty awesome.

Conclusion

The new context API is definitely easier to understand and reason about. It’s finally a first class citizen in the React world and I’m looking forward to use it in my projects.

More articles from Mark Pollmann

Understanding Redux Middleware by understanding redux-thunk

There has always been the notion that Redux middleware is some kind of black magic but the idea behind it is actually quite simple and…

January 31st, 2018 · 1 min read

Writing Custom Authorizers for AWS API Gateway

If you want to go serverless with your web app and you need an API running Lambda functions behind API Gateway on AWS is an excellent…

January 27th, 2018 · 4 min read
© 2019 Mark Pollmann
Link to $https://twitter.com/mark_pollmannLink to $https://github.com/MarkPollmannLink to $https://www.linkedin.com/in/mark-pollmann-961446132/