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 }56 render() {7 return /* ... */8 }9}1011MyProvider.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.context4 return `The color is ${color}`5 }6}78MyReceiver.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.node5 };67 static contextTypes = {8 router: PropTypes.object9 };1011 static childContextTypes = {12 router: PropTypes.object.isRequired13 };1415 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.match23 }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`34 class Provider extends Component {5 getChildContext() {6 return { [storeKey]: this[storeKey], [subscriptionKey]: null }7 }89 /* ... */1011 render() {12 return Children.only(this.props.children)13 }14 }1516 /* ... */1718 Provider.propTypes = {19 store: storeShape.isRequired,20 children: PropTypes.element.isRequired,21 }22 Provider.childContextTypes = {23 [storeKey]: storeShape.isRequired,24 [subscriptionKey]: subscriptionShape,25 }2627 return Provider28}
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.subscription12 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")23class 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")23class 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.