Utilising the Context API in React

Today, we're going to look at utilising the Context API in React applications and discuss some of the possible scenarios where you could consider using it.

Table of contents

What is the Context API?

Context is an invaluable part of a developer's toolkit for creating React applications.

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

Usually, you pass data down through components from parent to child. This can be frustrating when working with deeply nested components. Context alleviates this issue for us by allowing us to share this data without explicitly drilling it through components.

When should I use it?

You should consider using Context when you need to provide "global" data to components.

A good indicator that you could benefit from using Context is if you find yourself passing data through lots of deeply nested components.

What problems does it solve?

Context alleviates frustrations you may have when trying to provide data to numerous, potentially deeply nested components.

A few common examples of where we could effectively utilise Context include:

  • Sharing theme state
  • Sharing authentication state
  • Sharing language/translation state

These are all common pieces of "global" data that are often used by multiple different components at many different levels.

Let's look at a practical example next...

Examples

Context consists of two key parts, a provider and a consumer. As you'd expect, the provider provides the data, and the consumer consumes it.

Looking at the code examples below should aid you in understanding this concept.

Creating Context

Create a directory named context and create a file within it named ThemeContext.js.

We can create a Context by using the createContext method:

1import { createContext } from "react";
2
3const ThemeContext = createContext();
4
5export { ThemeContext };

Creating a Provider

Our ThemeContext that we just created contains a Provider component that we will use to provide our data to other components:

1const ThemeProvider = ({ children }) => {
2 return <ThemeContext.Provider>{children}</ThemeContext.Provider>;
3};

We will return our Context provider in this component and pass any children through it.

State in Context

Now, let's add some state to our Context provider:

1const ThemeProvider = ({ children }) => {
2 const [dark, setDark] = useState(true);
3
4 return <ThemeContext.Provider value={{ dark, setDark }}>{children}</ThemeContext.Provider>;
5};

Here we pass an object as a value prop to our provider which contains our theme state and the associated state setter. This allows us to consume this data from other components.

Your theme Context should look like this:

1import { createContext, useState } from "react";
2
3const ThemeContext = createContext();
4
5const ThemeProvider = ({ children }) => {
6 const [dark, setDark] = useState(true);
7
8 return <ThemeContext.Provider value={{ dark, setDark }}>{children}</ThemeContext.Provider>;
9};
10
11export { ThemeContext, ThemeProvider };

Creating a consumer

There's nothing special about consumers, they are just standard React components.

Let's create a Button component in which we will consume the data from our Context.

1const Button = () => {
2 return <button>Dark</button>;
3};
4
5export default Button;

Now we'll render this component in our App:

1const App = () => {
2 return <Button />;
3};

Note: Remember to import your Button component.

Wrapping consumers

You may have noticed earlier in the article that the ThemeProvider receives and renders children. This is because when using Context, consumers must be wrapped in their respective provider.

Let's wrap our Button in our ThemeProvider:

1const App = () => {
2 return (
3 <ThemeProvider>
4 <Button />
5 </ThemeProvider>
6 );
7};

Note: Remember to import your ThemeProvider component.

Consuming Context

Now it's time to consume the data from our Context in our Button component.

Consuming data from a Context is very simple. Simply import your Context and use the useContext hook to consume the data returned from our provider.

1const Button = () => {
2 const { dark } = useContext(ThemeContext);
3
4 return <button>{dark ? "Dark" : "Light"}</button>;
5};

Note: Remember to import your ThemeContext and the useContext hook.

Fantastic, we have access to our user state from within Button, without passing down anything through our components.

Updating state in a consumer

As well as consuming state, you can also update state from within your consumer components.

Let's add a simple toggle for our theme state:

1const Button = () => {
2 const { dark, setDark } = useContext(ThemeContext);
3
4 const handleClick = () => {
5 setDark((previousDark) => !previousDark);
6 };
7
8 return <button onClick={handleClick}>{dark ? "Dark" : "Light"}</button>;
9};

Let's step through this code:

  • We destructure our setDark state setter from our Context
  • We define a click handler which toggles the dark state
  • We add a click event to our button which invokes the click handler

Now, when we click the button, we toggle our theme.

Here's an example of our application:

Conclusion

It's worth noting that you can use multiple Contexts within a single React application, and even nest them too.

The above example is very simple, but hopefully it demonstrates how powerful Context can be and how it can be an invaluable tool in many React applications.

Hopefully you enjoyed this article and learnt a thing or two about the Context API.