React’s Context API in 5 Minutes
React’s new context API is pretty neat; it allows us to leverage the power of a single state object across an entire app, removing the need for passing props down through multiple components and reducing the risk along the way. All this without touching Redux. Since it’s a new feature the documentation is still pretty green, so I’ve summarised the essentials here in a quick read.
First, install the latest react
and react-dom
packages by running $ yarn add react@latest react-dom@latest
.
Initialise your context by using the createContext method available from the react
package:
import { createContext } from 'react';const AppContext = createContext();
Next, create a Provider component which will be the source of truth for your context:
class AppProvider extends Component {
state = {
name: 'Will',
}
render() {
const { children } = this.props;
return (
<AppContext.Provider
value={{
state: this.state,
changeName: () => this.setState({ name: 'Fred' }),
}}
>
{ children }
</AppContext.Provider>
);
}
}
There are two important points to note here. Firstly, the component we’ve created is a Provider
component. This denotes that it will be providing the context throughout the app. We will shortly be creating a Consumer
component, which consumes the context. Secondly, we are passing a value
prop to the Provider. This value refers to the value of the context. In this case, I’ve defined it as an object which includes the local state of the Provider component (which I will be using as context throughout the app), and a function to make a change to this state.
Now we need to create a Consumer component to access the context we’ve created as follows:
const Child = () => (
<AppContext.Consumer>
{context => (
<Fragment>
<p>I'm the consumer component. My name is {context.state.name}.</p>
<button onClick={context.changeName}>Change Name</button>
</Fragment>
)}
</AppContext.Consumer>
);
Note that we have created a Consumer
component and populated it with a render function. This render function exposes the value
prop we passed to the Provider
component, making our context available to the Consumer component. As we intended the button can be clicked to update the context, rather than the local state of the Child component
Finally, we need to create a main component which will wrap the Consumer component in the Provider component. This is essential, since any Consumer components will only be able to access context if they are a child of a Provider component (no matter how distant). I’ve created this as follows:
const App = () => (
<AppProvider>
<p>I'm the app component.</p>
<Child />
</AppProvider>
);
The Final Product
Put all of this together in one file, and you have a working example of React’s Context API in action:
import React, { Component, Fragment, createContext } from 'react';const AppContext = createContext();class AppProvider extends Component {
state = {
name: 'Will',
}
render() {
const { children } = this.props;
return (
<AppContext.Provider
value={{
state: this.state,
changeName: () => this.setState({ name: 'Fred' }),
}}
>
{ children }
</AppContext.Provider>
);
}
}const Child = () => (
<AppContext.Consumer>
{context => (
<Fragment>
<p>I'm the consumer component. My name is {context.state.name}.</p>
<button onClick={context.changeName}>Change Name</button>
</Fragment>
)}
</AppContext.Consumer>
);const App = () => (
<AppProvider>
<p>I'm the app component</p>
<Child />
</AppProvider>
);export default App;
Extra Bits
To split the context handling out into multiple files for use in more complex applications, simply export the context from a file as follows:
import { createContext } from 'react';const AppContext = createContext();export default AppContext;
From which it can then be imported into other components by way of import context from './context';
.
If using React Router, a straightforward solution is to inject the AppProvider
component at router level, thus making context available to all routes. For example:
import React from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
import AppProvider from './provider';
import PageOne from './pages/one';
import PageTwo from './pages/two';const Router = () => (
<BrowserRouter>
<AppProvider>
<Route exact path="/one" component={PageOne} />
<Route exact path="/two" component={PageTwo} />
</AppProvider>
</BrowserRouter>
);export default Router;