How (and when) to use React’s new Context API

March 29, 2018 0 Comments

How (and when) to use React’s new Context API

 

 

Hey guys, did you hear there’s a new React Context API?

So it’s not exactly news on the interwebs that React Context will be stable in the upcoming release of React, React 16.3.0. React Context is/was an experimental feature of React that received better implementation thanks to multiple PRs from contributors.

But even though it’s not news, it is worth taking a minute to learn when and how to use it. And that’s what we’ll do here.

React Context is a way for a child component to access a value in a parent component.

One familiar problem in React is what is popularly known as prop drilling.

Prop drilling occurs in situations where you’re looking to get the state from the top of your react tree to the bottom and you end up passing props through components that do not necessarily need them.

React Context solves the problem of props drilling. It allows you to share props or state with an indirect child or parent.

Ordinarily, you’d use a state management library like Redux or Mobx, but what if you don’t want to? Or the data to be passed is so minute that using a state management library would be overkill.

This is where the new React Context API comes in.

Well, the current Context API is considered to be unstable as it an experimental API and is likely to break in future releases of React. Also, the current and soon to be old Context API had issues with shouldComponentUpdate blocking context changes.

This happens when an intermediate component updates using shouldComponentUpdate, React will automatically assume there are no changes and reuse the entire subtree. The problem with that is if the subtree contains a context consumer, the consumer will not receive the latest context.

With the new React Context API, you should be familiar with three things:

React.createContext

React.createContext is used to initialise the Context and it’s passed the initial value. It returns an object with a Provider and a Consumer. Providers and consumers come in pairs, that is, for each provider, there is a corresponding consumer.

const Context = React.createContext();

Provider

The Provider component is used higher in the tree and accepts a prop called value.It provides a root upon which any child in that tree can access the values that are provided by that context provider.

render() {
return (
<Provider value={this.state.contextValue}>
{this.props.children}
</Provider>
);
}

The provider accepts a context value as a prop. Any matching consumer in the provider’s subtree can access it, regardless of how deeply it’s nested.

Consumer

The Consumer as the name suggests consumes the data being passed and renders the data by using a render prop API.

render() {
return (
<Consumer>
{contextValue => <Child arbitraryProp={contextValue} />}
</Consumer>
)
}

Before I demonstrate how React Context can be used, let’s take a look at this example below that uses prop drilling to get some data, so that we can establish a bit of…wait for it…context.

The code block above is just a short example of how prop drilling works. We had to pass the props data from the top to the bottom even though the components don’t necessarily need it.

Now let’s see how this can be solved using React Context. To get started, you have to install the alpha version of React 16.3.

npm i react@next react-dom@next

Let’s go through the code block above.

const JediContext = React.createContext();

We first initialise a new Context and set it to a const variable. It can be set as anything.

class JediProvider extends Component {
state = {
name: "Vader",
side: "dark"
};
render() {
return (
<JediContext.Provider
value={{
state: this.state,
turnGood: () =>
this.setState({
side: "good"
})
}}
>
{this.props.children}
</JediContext.Provider>
);
}
}

Inside the JediProvider Component, we create a Provider Context inside the render() function as JediContext.Provider . JediContext.Provider accepts a prop called value which is set to the state and a function that updates the state.

Now that we have a Provider, how do we consume the data, even across parent to child components?

class App extends Component {
render() {
return (
<JediProvider>
<Vader />
</JediProvider>
);
}
}
const Vader = props => {
return <Luke />;
};
const Luke = props => {
return <KyloRen />;
};
const KyloRen = props => {
return (
<JediContext.Consumer>
{context => (
<React.Fragment>
<p>My grandfather is {context.state.name} </p>
<p>He belonged to the {context.state.side} side</p>
<button onClick={context.turnGood}>Turn</button>
</React.Fragment>
)}
</JediContext.Consumer>
);
};
render(<App />, document.getElementById("root"));

The first thing to do would be to wrap all the children of the top level App component with the provider we created, which is, JediProvider . That means all of its children will have access to the value in the Context.

Almost like the prop drilling method above, the components are passed into each other albeit with no props, no drilling. The difference is in the bottom child which is KyloRen . The KyloRen component uses JediContext.Consumer to pass context as a render prop. Since context holds the state , you can easily use it to display the data that is required.

As seen with context.turnGood , we can also use Context to change and update the state. Whenever the button is clicked, it calls the turnGood function that was added as a value in the Provider.

Overall, this is a better solution than the prop drilling methods, you don’t have to pass props unnecessarily to component who don’t need them.

No. The new Context API has its limitations.

For example, it encourages the use of immutable or persistent data structures or strict comparison of context values which might prove difficult because many common data sources rely on mutation.

Another limitation is that the new Context API only allows for a Consumer to read values from a single Provider type, one consumer to one provider. Unlike the current API, which allows a Consumer to connect to multiple Providers.

You can use it when all you want is simple state management, or in cases where you want to pass some props deeply without the overkill that comes with Redux or MobX.

The Context API should be updated and included in the next couple of updates for React. It will definitely be interesting to see the updates and feature changes for the Context API going forward.

LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single page apps.

Try it for free.


Tag cloud