Getting started with React: Managing state using Context API

Arun Kant Pant
16 min readMay 3, 2020

This blog is a part of the series — Getting started with React.

In the previous part we discussed about how to make API calls, create mock servers and implement routing for your web application.

In this part, we will understand the significance of storing a central state for your application which you can subscribe to, using react’s Context API.

Storing a central state- the need

In our web applications for this series of blogs so far, we have relied on props to distribute state from a parent component to its children components. Also, we have worked with simple applications which only have one child or two children for a parent component to keep things simple to explain.

Often in the real world, life is not this easy. You will have multiple pages with their own state, being managed by different parent components responsible for each such page which can have many children components in return. These parents will often not be related to one another by any direct link, such as common ancestors.

Even when such a common ancestor relationship is present, consider the scenario where a common parent component might have many levels of nested child components and you need to share some state between two such children.

One of these children triggers an update, and another child needs to update itself according to this update as well. For whatever ‘x’ level the other child is nested at within their common parent (since the other child could even be the child of a child), we will always need to go through the parent to pass this update by drilling down to the child’s level using props, passing props again and again along the way until we reach the child component. This can result in really long prop chains.

Let us use Youtube for an example:

If we look at the page above, it can be divided into any number of components.

We will try to imagine how we might try turning on the dark theme for the components of such a page, obviously without using a concrete centralised state management strategy for now. Also please bear with me, as we will be imagining our component composition in a manner for this page above to suit our examples for the two common cases we talked about before.

Case A, independent parents with no direct link

Imagine the page to be divided into 3 main parent components — The titlebar with the search option, The video player section and the sidebar with the next videos in the playlist.

We use the dropdown from the titlebar to toggle the button state (which again could in itself be a separate reusable component, since a toggle button might be present at multiple places).

The ‘Titlebar’ parent component holds a local component state for the dark theme, just like the ‘VideoSection’ component and the ‘Sidebar’ component.

On toggling its child ToggleButton component as shown in the gif above, imagine that we fire a setState update in Titlebar and update the status of its dark theme flag.

But now we need to update the same flag for the video player and the sidebar as well, who have their own local states independent of one another.

How do we share this dark theme status update with them, in an efficient and predictable manner within the React ecosystem?

This brings us to case B, creating a common parent for even these three components to distribute a common state as props: by creating the MainPage component.

Case B, common parent and ‘siblings’ at different levels

Let us now imagine that we created a common ‘MainPage’ parent component. This now holds our dark theme flag and distributes it to its 3 children via props: TitleBar, VideoSection and Sidebar.

Now each one of these are in turn also Parents to other components, since each has to maintain different states and UI.

For eg. the TitleBar has to maintain the search query state for its search bar, the VideoSection has to maintain the play/pause state for a video and the Sidebar has to maintain the show thumbnail video preview state.

Inheriting props from their parent MainPage and then setting own state according to these inherited props can create scope for a lot of bugs and problems. For eg. the TitleBar has to also change the background of its search bar to a dark shade even darker than the rest of the page. It will need to further pass the props it received, into its own child Searchbar component as props.

Another example is the Youtube comments section, which can be seen as a child of VideoSection.

Font colour changing for the comments section

The text colour for the VideoSection’s child component, displaying the list of comments, will also need to respond to this update. Hence again we will need to pass state as props to VideoSection, and then these props as props again onto this component’s child for rendering font colour updates.

You can imagine that this can soon prove hard to track consistently, and this major issue is also aptly called prop drilling. This is one of the major potential problems you might face when not having a central state to distribute such shared states.

You will come across many such scenarios in real world projects, where you have to share some common state between components and also update it in a consistent manner for other components which might depend on this state.

Let us now have a look at how React offers us a way out.

The Context API

The context API is a solution which can be used to eliminate problems like long prop chains, as we saw in the examples before. It is a part of react itself, and can be used to create a context or central state, which we can provide wherever required via what we call ‘Providers’.

To subscribe or to access this central state or ‘context’ we use ‘Consumers’, which as the name implies are used to consistently consume or use this state for our purpose.

This will become clearer in a bit with our example project, so let’s finally start with some coding!

We will be creating a simple application, where we will (unsurprisingly) change the theme of a web app using a toggle switch. We will use a free API to fetch a list of posts, to display these posts and provide the option to remove them as well.

First, generate your react project using create-react-app:

create-react-app posts

Again, convert your App component to a class based component first.

As I have mentioned in previous parts of this series, this is a progressive build up to every major concept in react web development, and we will be looking at react hooks and pure functional development in separate future posts.

This is what your App component should look like in the beginning:

import React, { Component } from 'react';
import './App.css';
class App extends Component {
render () {
return (
<div className="App">
<h1>Posts</h1>
</div>
);
}
}
export default App;

First, let us add a dark theme state to our App component.

class App extends Component {
state = {
darkTheme: false
};
render () {
return (
<div className="App">
<h1>Posts</h1>
</div>
);
}
}

Now, we’ll use JSON placeholder to fetch our list of posts. It exposes a list of posts for the endpoint:

https://jsonplaceholder.typicode.com/posts

Let us keep things simple and use the standard javascript fetch API to make a request. We will create a new class based component called Posts, which will aggregate this list of posts from the endpoint.

Create a new directory in src called Containers, and in it a folder called Posts. Add a file Posts.js to this folder:

import React, { Component } from 'react';class Posts extends Component {
state = {
posts: []
}
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(responseData => this.setState({ posts: responseData }));
}
render() {
return (
<div>List of Posts</div>
);
}
};
export default Posts;

Our state will now be populated with the response data.

Import this component into App.js:

import Posts from './Containers/Posts/Posts';

And change your App component to look like this:

class App extends Component {
state = {
darkTheme: false,
};
render() {
return (
<div className="App">
<h1>Posts</h1>
<Posts />
</div>
);
}
}

Your app should look something like this:

Showing a text for now where our list will be populate

And if you check your network calls in the browser dev tools, you will see we are fetching the list of posts:

Network call showing the data we received

Let us now create a component to display these fetched posts as cards/tiles with a brief description.

Create a folder to house your stateless components, called Components. Inside this folder create another folder called Post, and add a Post.js file to it:

src/Components/Post/Post.js

We will use this file Post.js to create our cards/tiles for each post. Add the following code to it:

import React from 'react';const post = (props) => {
return (
<div style={{
border: '1px solid #ccc',
borderRadius: '5px',
padding: '10px',
width: '20%',
margin: '10px'
}}>
<h3>{props.title}</h3>
<p>{props.body}</p>
</div>
);
};
export default post;

We will now import this component into our Posts class based component and populate it with data for each post:

import Post from '../../Components/Post/Post';

And modify your render( ) method:

...render() {
return (
<div
style={{
display: 'flex',
justifyContent: 'center',
flexWrap: 'wrap'
}}
>
{
// check if posts exist
this.state.posts.length
? (
this.state.posts.map(post => (
// display each post
<Post
key={post.id}
title={post.title}
body={post.body}
/>

))
)
: (
// show message if no posts
<h3>No Posts</h3>
)
}
</div>
);
}
...

You should end up with something like this in the browser:

Now, we will proceed to create a button to toggle the state of the theme. In our dark theme, we are aiming to obtain something along the lines of the following effect:

The lighter grey (#ccc) is the background colour for the class based Posts component. The darker grey (#4a4a4a) and white font colour is for the functional (and hence stateless), individual Post component.

So we will need to acquire the dark theme flag status at two levels of nesting, and for two different types of components (class based and functional).

Let us see how this will be done.

First, we will create a toggle button. There will be a separate component for the button. This will allow us to reuse it for the delete button for each post as well. And, to make things fun, we will make it a part of our theme as well.

Create a new Button.js file, at the path src/Components/Button, and add the following code to it:

import React from 'react';const button = (props) => {
return (
<div>
<button
style={{
padding: '10px',
border: '1px solid #ccc',
background: '#4a4a4a',
color: '#fff'
}}
onClick={props.clickHandler}
>
{props.label}
</button>
</div>
);
};
export default button;

Now we will import this stateless component into our App component, and place it under our h1 tag.

import Button from './Components/Button/Button';

And the code changes in your App component:

...  themeChangeHandler = () => {
this.setState({ darkTheme: !this.state.darkTheme });
};
render() {
return (
<div className="App">
<h1>Posts</h1>
<Button
label="Change Theme"
clickHandler={this.themeChangeHandler}
/>

<Posts />
</div>
);
}
...

We created a method named themeChangeHandler and linked it to our Button component’s clickHandler prop.

Your page should now be looking like this:

Page with our theme toggle button component

Great, we can now begin with the main objective: using the Context API.

Step 1: creating a context

We will first create a new folder called Context in our project’s src directory. To this, we will add a file called ThemeContext.js:

import React from 'react';const ThemeContext = React.createContext({ darkTheme: false });export default ThemeContext;

We use react’s createContext( ) method to create a context with a default value passed into it as an argument.

The default value will almost always be overwritten by the ‘Provider’ as we will see in a bit, and will only be passed on in niche cases such as when testing components and there is no ‘Provider’ present to pass on the context.

Step 2: Creating a Provider

A Provider component is needed to use the context, bind it to a value and then forward this value down to children components which might require this value.

The children can later on subscribe to the context using a consumer directly.
If we wrap a component with a Provider, then that component along with all its children can now subscribe to this Provider directly.

In our case, we have a Posts component with many individual Post components as its children.

We will see this in action by first changing the theme only for the individual Post components, to directly respond to theme change in the state of our App component. Hence, skipping over the main Posts component, which would not have been possible with props.

In your App component, import the created context:

import ThemeContext from './Context/ThemeContext';

Now, every such context exposes a Provider and a Consumer object. We will use the Provider to wrap the Posts component, and also the Button component since we will be changing its theme later too.

In the render( ) method for App.js, make the changes as follows :

...render() {
return (
<div className="App">
<h1>Posts</h1>
<ThemeContext.Provider
value={
{ darkTheme: this.state.darkTheme }
}
>

<div>
<Button
label="Change Theme"
clickHandler={this.themeChangeHandler}
/>
<Posts />
</div>
</ThemeContext.Provider>
</div>
);
}
...

The <ThemeContext.Provider> component takes a value prop, and it is this value prop which any of the components wrapped by our Provider can subscribe to.

In case we had not specified a value prop, our default value would have been picked up. Like I said, you will almost always provide your own value to the aptly named value prop.

Step 3: Consuming the value provided by the Provider

How to consume the value provided by our provider differs for class based and functional components.

We will first change the theme for our functional and stateless individual Post component.

First, import the ThemeContext in your src/Components/Post/Post.js file for the individual Post component:

import ThemeContext from '../../Context/ThemeContext';

As I mentioned before, a context exposes a Provider and Consumer on it. Just as we accessed the provider using <ThemeContext.Provider>, we will use our consumer as shown below:

...const post = (props) => {
return (
<ThemeContext.Consumer>
{value => (
<div style={{
border: '1px solid #ccc',
borderRadius: '5px',
padding: '10px',
width: '20%',
margin: '10px'
}}>
<h3>{props.title}</h3>
<p>{props.body}</p>
</div>
)}
</ThemeContext.Consumer>
);
};
...

First, we wrap the previous JSX code with our <ThemeContext.Consumer> component’s opening and closing tags. This is similar to how we created our Provider.

The difference is that this gives us access to the value passed by the Provider, which is passed on in the form of a function child as shown above. By wrapping the previous JSX with curly braces, into a function with a value argument which is passed in by your Consumer.

This value is subscribed to the value prop in our Provider, and will update along with it.

Basically the structure of your consumers for any functional components look like this:

<MyContext.Consumer>
{value => /* render something based on the context value */}
</MyContext.Consumer>

Everything wrapped within the function body will have access to this value, and we wrap our JSX with it.

We will therefore use this to conditionally change the colour codes for our Post component (src/Components/Post/Post.js) :

const post = (props) => {
return (
<ThemeContext.Consumer>
{value => (
<div style={{
border: '1px solid #ccc',
borderRadius: '5px',
background: value.darkTheme
? '#4a4a4a'
: '#fff',
color: value.darkTheme
? '#fff'
: '#000',
padding: '10px',
width: '20%',
margin: '10px'
}}>
<h3>{props.title}</h3>
<p>{props.body}</p>
</div>
)}
</ThemeContext.Consumer>
);
};

After this, you will be able to change the theme for your individual Post components.

So we see, that we can directly react to changes in the local state of our App component all the way from our functional and stateless Post component, without needing any prop chaining.

Let us now see how we can change the theme for the rest of the page.

We have our Posts component to deal with first, this gives us an opportunity to see how class based components can subscribe to a provider.

First, open your src/Containers/Posts/Posts.js file, where we have the code for our main Posts component. As before, first import our context:

import ThemeContext from '../../Context/ThemeContext';

Now in the case of class based components, the process of consuming is a bit different. You could of course consume your context as before, by wrapping with a consumer component and receiving the value in a child function. However, there is a simpler way, add the following line to your Posts component code:

class Posts extends Component {
state = {
posts: []
}
static contextType = ThemeContext; componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(responseData => this.setState({ posts: responseData }));
}
...}

If you’re familiar with the static keyword, you know that it is used to access properties in a class without instantiating an object.

Because of this contextType, We receive a this.context property to use in our class, similar to how we have access to this.props to access props in a class based component.

We can now use this, and conditionally set our theme (note the bolder text):

class Posts extends Component {
state = {
posts: []
}
static contextType = ThemeContext; componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(responseData => this.setState({ posts: responseData }));
}
render() {
return (
<div
style={{
display: 'flex',
justifyContent: 'center',
flexWrap: 'wrap',
background: this.context.darkTheme
? '#ccc'
: '#fff'
}}
>
{
// check if posts exist
this.state.posts.length
? (
this.state.posts.map(post => (
// display each post
<Post
key={post.id}
title={post.title}
body={post.body}
/>
))
)
: (
// show message if no posts
<h3>No Posts</h3>
)
}
</div>
);
}
};

After this, you will be able to toggle your background theme for the Posts component as well. Also, you can now pass on this.context as a prop to the individual Post components too. Basically use it just as you would use any other property on the class.

Your app should now be looking something like this:

I will leave the task of changing the theme for the Change Theme button and the heading up to you as an exercise for practice.

Let us now work on deleting individual posts from our list.

For this, we will first need to maintain our list of posts in the App component, and distribute it from the Provider specified there.

Therefore, shift the API call from your Posts component so that it is left looking like this:

...class Posts extends Component {static contextType = ThemeContext;render() {
return (
<div
style={{
display: 'flex',
justifyContent: 'center',
flexWrap: 'wrap',
background: this.context.darkTheme
? '#ccc'
: '#fff'
}}
>
{
// we will now pick posts from context
this.context.posts.length
? (
this.context.posts.map(post => (
// display each post
<Post
key={post.id}
title={post.title}
body={post.body}
/>
))
)
: (
// show message if no posts
<h3>No Posts</h3>
)
}
</div>
);
}
};
...

We have moved out the API call and posts from the local state, and are expecting it to be passed to us by the Provider instead.

Now in our App component, we will do a couple of things:

class App extends Component {
state = {
darkTheme: false,
posts: [],
};
themeChangeHandler = () => {
this.setState({ darkTheme: !this.state.darkTheme });
};
postDeleteHandler = (id) => {
const updatedPosts = (
this.state
.posts
.filter(post => post.id !== id)
);
this.setState({posts: updatedPosts});
};
componentDidMount() {
fetch('
https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(responseData => this.setState({ posts: responseData }));
}
render() {
return (
<div className="App">
<h1>Posts</h1>
<ThemeContext.Provider value={
{
darkTheme: this.state.darkTheme,
posts: this.state.posts,
deleteHandler: this.postDeleteHandler
}
}>
<div>
<Button
label="Change Theme"
clickHandler={this.themeChangeHandler}
/>
<Posts />
</div>
</ThemeContext.Provider>
</div>
);
}
}

We have

  • added our posts to the state,
  • created a handler method to delete any posts which match a given id (which we get with our mock response)
  • added a componentDidMount( ) lifecycle hook to make our API call and update the state
  • distributed our posts and the delete handler via our ThemeContext Provider’s value prop

Now we will import our Button component in our individual Post component, and use it to execute the deleteHandler method available in our consumer. In your src/Components/Post/Post.js file:

import React from 'react';
import ThemeContext from '../../Context/ThemeContext';
import Button from '../Button/Button';
const post = (props) => {
return (
<ThemeContext.Consumer>
{value => (
<div style={{
border: '1px solid #ccc',
borderRadius: '5px',
background: value.darkTheme
? '#4a4a4a'
: '#fff',
color: value.darkTheme
? '#fff'
: '#000',
padding: '10px',
width: '20%',
margin: '10px'
}}>
<h3>{props.title}</h3>
<p>{props.body}</p>
<Button
label="Delete"
clickHandler={() => value.deleteHandler(props.postId)}
/>

</div>
)}
</ThemeContext.Consumer>

);
};
export default post;

Now all we need to do is pass on the ‘postId’ prop we are using above, from the main Posts component. In your src/Containers/Posts/Posts.js file, simply add the postId prop to your Post component:

...  <Post
key={post.id}
title={post.title}
body={post.body}
postId={post.id}
/>
...

And now, you should be able to delete posts as well along with changing the theme.

Posts being delete for different themes

So we see how we can also pass on methods via our Providers to nested Child components, avoiding unnecessary prop drilling issues.

Before we part, there is another thing to note from the official docs:

All consumers that are descendants of a Provider will re-render whenever the Provider’s value prop changes. The propagation from Provider to its descendant consumers (including .contextType and useContext) is not subject to the shouldComponentUpdate method, so the consumer is updated even when an ancestor component skips an update.

So if you’re going to use this in a project where you’ve also used the shouldComponentUpdate method, be mindful of this fact! This can also prove to be among a few of the cons of this state management strategy, as we lose control over optimising our renders by cancelling updates when required.
Another thing you may consider to be somewhat of a con, is that to share state even with the Context API, you will need to create a common ancestor somewhere from where to describe the scope of your Provider. In our example, we used the App component for this.

In the next part of this series, we will have a look at Redux, and how it also offers us a way to manage our state effectively.

Okay so that was it for this blog, as always:

Happy Coding!

--

--

Arun Kant Pant

Building things, breaking things, learning things. Frontend Engineer at The Economist Intelligence Unit.