Context is currently an experimental API for React – but soon to be a first class citizen! There are a lot of reasons it is interesting but perhaps the most is that it allows for parent components to pass data implicitly to their children, no matter how deep the component tree is. In other words, data can be added to a parent component and then any child can tap into it.
See the Pen React Context Lights by Neal Fennimore (@nealfennimore) on CodePen.
While this is often the use case for using something like Redux, it’s nice to use if you do not need complex data management. Think about that! We create a custom downstream of data, deciding which props are passed and at which levels. Pretty cool.
Context is great in areas of where you have a lot of components that depend on a single piece of data, but are deep within the component tree. Explicitly passing each prop to each individual component can often be overwhelming and it is a lot easier just to use context here.
For example, let’s consider how we would normally pass props down the tree. In this case, we’re passing the color red
using props on each component in order to move it on down the stream.
class Parent extends React.Component {
render(){
return <Child color="red" />;
}
}
class Child extends React.Component {
render(){
return <GrandChild color={this.props.color} />
}
}
class GrandChild extends React.Component {
render(){
return (
<div style={{color: this.props.color}}>
Yep, I'm the GrandChild
</div>
);
}
}
What if we never wanted the Child
component to have the prop in the first place? Context saves us having to go through the Child
component with color and pass it directly from the Parent
to the GrandChild
:
class Parent extends React.Component {
// Allow children to use context
getChildContext() {
return {
color: 'red'
};
}
render(){
return <Child />;
}
}
Parent.childContextTypes = {
color: PropTypes.string
};
class Child extends React.Component {
render() {
// Props is removed and context flows through to GrandChild
return <GrandChild />
}
}
class GrandChild extends React.Component {
render() {
return (
<div style={{color: this.context.color}}>
Yep, I'm still the GrandChild
</div>
);
}
}
// Expose color to the GrandChild
GrandChild.contextTypes = {
color: PropTypes.string
};
While slightly more verbose, the upside is exposing the color
anywhere down in the component tree. Well, sometimes…
There’s Some Gotchas
You can’t always have your cake and eat it too, and context in it’s current form is no exception. There are a few underlying issues that you’ll more than likely come into contact with, if you end up using context for all but the simplest cases.
Context is great for being used on an initial render. Updating context on the fly? Not so much. A common issue with context is that context changes are not always reflected in a component.
Let’s dissect these gotchas in more detail.
Gotcha 1: Using Pure Components
Context is hard when using PureComponent
, since by default it does not perform any shallow diffing with context. Shallow diffing with PureComponent
is testing for whether the values of the object are strictly equal. If they’re not, then (and only then) will the component update. But since context is not checked, well… nothing happens.
See the Pen React Context Lights with PureComponents by Neal Fennimore (@nealfennimore) on CodePen.
Gotcha 2: Should Component Update? Maybe.
Context also does not update if a component’s shouldComponentUpdate
returns false
. If you have a custom shouldComponentUpdate
method, then you’ll also need to take context into consideration. To enable updates with context, we could update each individual component with a custom shouldComponentUpdate
that looks something like this.
import shallowEqual from 'fbjs/lib/shallowEqual';
class ComponentThatNeedsColorContext extends React.PureComponent {
// nextContext will show color as soon as we apply ComponentThatNeedsColorContext.contextTypes
// NOTE: Doing the below will show a console error come react v16.1.1
shouldComponentUpdate(nextProps, nextState, nextContext){
return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState) || !shallowEqual(this.context, nextContext);
}
}
ComponentThatNeedsColorContext.contextTypes = {
color: PropTypes.string
};
However, this does not solve the issue of an intermediary PureComponent
between the parent and the child blocking context updates. This means that every PureComponent
between the parent and child would need to have contextTypes
defined on it, and they would also need to have an updated shouldComponentUpdate
method. And at this point, that’s a lot of work for very little gain.
Better Approaches to the Gotchas
Fortunately, we have some ways to work around the gotchas.
Approach 1: Use a Higher Order Component
A Higher Order Component can read from context and pass the needed values on to the next component as a prop.
import React from 'react';
const withColor = (WrappedComponent) => {
class ColorHOC extends React.Component {
render() {
const { color } = this.context;
return <WrappedComponent style={{color: color}} {...this.props} />
}
}
ColorHOC.contextTypes = {
color: React.PropTypes.string
};
return ColorHOC;
};
export const Button = (props)=> <button {...props}>Button</button>
// ColoredButton will render with whatever color is currently in context with a style prop
export const ColoredButton = withColor( Button );
See the Pen React Context Lights with HOC by Neal Fennimore (@nealfennimore) on CodePen.
Approach 2: Use Render Props
Render Props allow us to use props to share code between two components.
class App extends React.Component {
getChildContext() {
return {
color: 'red'
}
}
render() {
return <Button />
}
}
App.childContextTypes = {
color: React.PropTypes.string
}
// Hook 'Color' into 'App' context
class Color extends React.Component {
render() {
return this.props.render(this.context.color);
}
}
Color.contextTypes = {
color: React.PropTypes.string
}
class Button extends React.Component {
render() {
return (
<button type="button">
{/* Return colored text within Button */}
<Color render={ color => (
<Text color={color} text="Button Text" />
) } />
</button>
)
}
}
class Text extends React.Component {
render(){
return (
<span style={{color: this.props.color}}>
{this.props.text}
</span>
)
}
}
Text.propTypes = {
text: React.PropTypes.string,
color: React.PropTypes.string,
}
Approach 3: Dependency Injection
A third way we can work around these gotchas is to use Dependency Injection to limit the context API and allow components to subscribe as needed.
The New Context
The new way of using context, which is currently slated for the next minor release of React (16.3), has the benefits of being more readable and easier to write without the “gotchas” from previous versions. We now have a new method called createContext
, which defines a new context and returns both a Provider
and Consumer
.
The Provider
establishes a context that all sub-components can hook into. It’s hooked in via Consumer
which uses a render prop. The first argument of that render prop function, is the value
which we have given to the Provider
. By updating the value within the Provider
, all consumers will update to reflect the new value.
As a side benefit with using the new context, we no longer have to use childContextTypes
, getChildContext
, and contextTypes
.
const ColorContext = React.createContext('color');
class ColorProvider extends React.Component {
render(){
return (
<ColorContext.Provider value={'red'}>
{ this.props.children }
</ColorContext.Provider>
)
}
}
class Parent extends React.Component {
render(){
// Wrap 'Child' with our color provider
return (
<ColorProvider>
<Child />
</ColorProvider>
);
}
}
class Child extends React.Component {
render(){
return <GrandChild />
}
}
class GrandChild extends React.Component {
render(){
// Consume our context and pass the color into the style attribute
return (
<ColorContext.Consumer>
{/* 'color' is the value from our Provider */}
{
color => (
<div style={{color: color}}>
Yep, I'm still the GrandChild
</div>
)
}
</ColorContext.Consumer>
);
}
}
Separate Contexts
Since we have more granular control in how we expose context and to what components are allowed to use it, we can individually wrap components with different contexts, even if they live within the same component. We can see this in the next example, whereby using the LightProvider
twice, we can give two components a separate context.
See the Pen React Context Lights with new Context by Neal Fennimore (@nealfennimore) on CodePen.
Conclusion
Context is a powerful API, but it’s also very easy to use incorrectly. There are also a few caveats to using it, and it can be very hard to figure out issues when components go awry. While Higher-Order Components and dependency injection offer alternatives for most cases, context can be used beneficially in isolated portions of your code base.
With the next context though, we no longer have to worry about the gotchas we had with the previous version. It removes having to define contextTypes
on individual components and opens up the potential for defining new contexts in a reusable manner.
So this 1) solves a problem already solved by flux/redux 2) increases the number of lines of code needed to do the same thing 3) increases the number of things I, as a developer, has to learn 4) still comes with a number of gotchas. Why do we as developers do this to ourselves?
1) This doesn’t make sense. Most Flux/Redux implementations use React Context internally. React Context is getting a new API that gets rid of the caveats around the old API, so in theory Flux/Redux implementations can benefit from that as well. On top of that, if you’re using Flux/Redux only to avoid passing down props, you’re using them wrong. You should just use Context directly.
2) In many cases, it reduces the number of lines needed. But anyway, the increase in clarity/explicitness of the new API should be worth an increased line count.
3) Oh my. Just forget the old API and re-use the freed-up brain space for the new one? ;)
4) Did you actually finish reading the article?
@saynothingetal I’m sure this is obvious, but the point of ‘context’ is to keep react’s VIEW as native as possible. While we all had to run the gamut of the flux and Redux patterns, they we often “un-opinonated” (THey weren’t “built” for react). This can be cumbersome when trying to install ReactJS library and “get going” on your view. So while you can be discouraged by learning the new “context” api, I would suggest sticking with it, because there isn’t a lot of times you will need to implement Redux/Flux libraries, and this API “should” make the line less blurry on realizing if you do/don’t.
Great article, I just would like to suggest to maybe create examples on how to export your consumer (I doubt everyone writes a ONE file when using React, and I know it took me some trial-and-error to have React.Consumer pass my state).
Yes, definitely! That’s a great suggestion. The beauty of the new context api is that it’s highly reusable, so we can store both the Provider and Consumer in a single file, and use them wherever we want.
I’m sorry but i don’t understand why the new context api can be a great news ?
Doesn’t it break the data flow comprehension ?
How can i know where data come from ? Only with ctrl+f the same context provider name ?
It reminds me of a GOTO for data.
If it becomes difficult to pass data through many components, why not pass components instead :
https://medium.com/@RubenOostinga/avoiding-deeply-nested-component-trees-973edb632991
Thx
Those are some interesting questions, Step. Let’s see if we can break them down some.
1) Why is the new context api great news?
For me, it’s because the new API provides so much more compared to the old API. No more having to define a getChildContext method on a parent. No more needing to define a bunch of context related prop types everywhere. It’s less mentally taxing and easier to figure out what’s going on. I can also reuse the same context provider/consumer and just have it work.
2) Does it break data flow comprehension?
I would say yes, it does break it somewhat. Especially, if you have deeply nested components, having to go back up and trace where the context is being defined, could be troublesome. Though one could argue that this is just as mentally taxing as prop drilling.
Hopefully, in these cases, you’d have the context provider somewhere higher up in the component tree, or at least have multiple providers used in a single file, for easier updates.
3) How can I know where the data comes from?
I’d say it depends on what you’re trying to accomplish. If you’re building a complex application, you should probably be using something like redux (which is also using context behind the scenes).
Context, at least for me, can be used for more one-off use cases. Something like changing the theme of an app, is a common use case for using the context api.
4) Why not pass components?
This is a great point and often this can be done. I’d say that a problem with this is approach though is components that are deeply nested within another. We often do not have access to components deep within a tree to do this, and having to nest components correctly in every hypothetical UI state, would be a lot of more difficult then wrapping it within context, and letting it reflect based on that context.