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";23const ThemeContext = createContext();45export { 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);34 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";23const ThemeContext = createContext();45const ThemeProvider = ({ children }) => {6 const [dark, setDark] = useState(true);78 return <ThemeContext.Provider value={{ dark, setDark }}>{children}</ThemeContext.Provider>;9};1011export { 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};45export 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);34 return <button>{dark ? "Dark" : "Light"}</button>;5};
Note: Remember to import your
ThemeContext
and theuseContext
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);34 const handleClick = () => {5 setDark((previousDark) => !previousDark);6 };78 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.