What is React Context API?
React.js is a powerful way to make sophisticated web apps and websites. If you’re not yet familiar with React, Hooks, or APIs, or just want a refresher, check out these tutorials:
React Contexts build on React’s state management. They provide a way to manage complex, nested state in a simpler and more effective way than using props.
Managing nested state
Any real-world React app is going to need to share state, or data, between different components at different levels of the React.js hierarchy. They also need to share functions that can act on or change that data.
There are three main ways to do this:
- Props: Store state directly in the common ancestor component, and pass it down through as many components as needed, as props, until it reaches the target components. This has the advantage of being simple and easily understandable, but if the target components are very deep or very many, it can get cumbersome to pass the state through so many components. This clutters the props of each component in the hierarchy. It also requires them to know more than they need to about their children and their parent.
- Library: Use a library like Redux or MobX to manage state for you. This might be quicker at first, but such libraries inevitably add a non-trivial amount of complexity and come with a learning curve of their own. This might not be a bad thing depending on your project’s requirements. But the rule of good software development is to always try the simplest solution first.
- React Context API: Store the state in a Context value in the common ancestor component (called the Provider Component), and access it from as many components as needed (called Consumer Components), which can be nested at any depth under this ancestor. This solution has the same benefits as the Props solution, but because of what could be called “hierarchical scoping”, it has the added benefit that any component can access the state in any Context that is rooted above itself in React’s hierarchy, without this state needing to be passed down to it as props. React.js takes care of all the magic behind the scenes to make this work.
When developing a React app, the primary situations where the React Context API really shines are:
- When your state needs to be accessed or set from deeply nested components.
- When your state needs to be accessed or set from many child components.
The basics of React Context API
There are three aspects to using React Contexts:
- Defining the Context object so we can use it. If we wanted to store data about the current user of a web app, we could create a
UserContext
that can be used in the next two steps:// Here we provide the initial value of the context const UserContext = React.createContext({ currentUser: null, });
Note: It doesn’t matter where this Context lives, as long as it can be accessed by all components that need to use it in the next two steps.
- Providing a value for a Context in the hierarchy. Assuming you had an
AccountView
component, you might provide a value like this:const AccountView = (props) => { const [currentUser, setCurrentUser] = React.useState(null); return ( {/* Here we provides the actual value for its descendents */} <UserContext.Provider value={{ currentUser: currentUser }}> <AccountSummary/> <AccountProfile/> </UserContext.Provider> ); };
- Accessing the current Context value lower in the hierarchy. If the
AccountSummary
component needed the user, we could have just passed it as a prop. But let’s assume that it doesn’t directly access the user data, but rather contains another component that does:// Here we don't use the Context directly, but render children that do. const AccountSummary = (props) => { return ( <AccountSummaryHeader/> <AccountSummaryDashboard/> <AccountSummaryFooter/> ); };
We can reasonably expect that all three of these child components will want to access the current user’s data. But to keep things simple, let’s just look at the
AccountSummaryHeader
component:const AccountSummaryHeader = (props) => { // Here we retrieve the current value of the context const context = React.useContext(UserContext); return ( <section> <h2>{context.currentUser.name}</h2> </section> ); };
In the above examples, we did three things:
- Created a
UserContext
object. - Set a root value in
AccountView
. - Accessed this value deep within
AccountSummaryHeader
.
We’re also using the useContext
React Hook. If you need a refresher on this, check out the official useContext
documentation.
How does React Context API work with the Render cycle?
Every time the Provider Component renders, the value prop of its Context object may trigger a re-render of the component tree just as if it had passed a different prop to one of its child components. When this happens, any Consumer Component lower in that tree will re-render, with the new context value. If using the useContext hook, this will have the new value of the hook during the new render.
Building a Multi-city Weather app using React Context API
To understand how React’s Context feature can help us, let’s look at a scenario where the React Context API would make things simpler for us.
We’ll make a simple web app that lets users view the current temperature in multiple cities, and shows the average of these temperatures. The user can add multiple cities, and the app will also show an average of all the temperatures listed.
At the end of this tutorial, this is what it will look like:
Prerequisites
Before continuing, make sure you have the following:
- NPM and Node.js, which you can download from this page.
- An IDE or code editor, such as VS Code.
1. Set up the project
We’ll be using Create React App as a starting point for our app, because it’s very simple when you just want to start making a new React app without having to configure Webpack, JSX, or Babel, since Create React App (or CRA) comes with all of these pre-configured for you.
Run the following commands:
$ npx create-react-app react-context-tutorial $ cd react-context-tutorial $ npm start
Then wait for the sample page to load in your browser. You should see the following demo React app page:
When you make any changes in your editor to the source files, the page will auto-reload to reflect your latest changes during development.
2. Lay the code foundation
Define the Context
First, let’s create the Context. Open up src/App.js
in your favorite code editor or IDE, and add the following to the top:
const WeatherContext = React.createContext({ cities: [], addCity: (name, temperature) => { }, });
This React Context stores two things:
- An array of cities. Each object will have a
name
andtemperature
key. Since we don’t start with any cities, this initial array is empty. - A function to add cities to this array. It takes the name and temperature, which we’ll add to the
cities
array. We give this a dummy function for now, since this initial value doesn’t have access to our root component’s state.
Replace the logic in our App component
The App component is going to be our common ancestor, which provides the Context value for all its descendants. This component is “in charge” of the Context, so to speak.
Replace the App
function definition with the following code:
function App() { const [cities, setCities] = React.useState([]); const addCity = (name, temperature) => { const newCity = { name, temperature }; setCities(prevCities => [...prevCities, { name, temperature }]); }; return ( <WeatherContext.Provider value={{ cities, addCity, }}> <div className="city-overview"> <h2>Multi-Weather App</h2> <CityList /> <AddCityButton /> <TemperatureAverage /> </div> </WeatherContext.Provider> ); }
We’re doing a few things here:
- We use React Hooks to keep track of the array
cities
and create a settersetCities
. - We create an
addCity
function that just wraps thesetCities
, which we’ll add into the Context. - We provide the root value in the hierarchy using
WeatherContext.Provider
. - We start using child components, such as
CityList
, under this provider.
NOTE: When you change this code, you will see that the code currently fails to compile. Don’t worry, that’s expected! We haven’t created any of the child components yet.
3. Define the CityList component
Add this code just above the definition of App:
const CityList = (props) => { const context = React.useContext(WeatherContext); return ( <table className="city-list"> <thead> <tr> <th>City</th> <th>Temperature</th> </tr> </thead> <tbody> {context.cities.map((city, i) => ( <tr key={city.name}> <td>{city.name}</td> <td>{city.temperature}</td> </tr> ))} </tbody> </table> ); };
This is the first consumer of our WeatherContext
React Context. It’s straightforward: grab the list of cities, and map them into a table, showing both the name and the temperature.
4. Define the TemperatureAverage component
Add this just above the App definition:
const TemperatureAverage = (props) => { const context = React.useContext(WeatherContext); if (context.cities.length === 0) { return ( <div>Add some cities to view their average temperatures.</div> ); } let total = 0; for (const city of context.cities) { total += city.temperature; } const avg = total / context.cities.length; return ( <div> The average is <b>{avg.toFixed(2)}</b> degrees Fahrenheit. </div> ); };
This is the second consumer of WeatherContext
, and it’s slightly more complex: we add up the average of each city’s temperature, and divide by the length of the array, to get the average. If there are no cities yet, this would divide by 0, so we show a simple message to the user instead.
5. Define the AddCityButton component
So far, we’ve only used the cities
property of our custom React Context. The AddCityButton
component is what’s finally going to use the addCity
property. Add this component just above the App
function definition:
const AddCityButton = (props) => { const context = React.useContext(WeatherContext); const [name, setName] = React.useState(''); const submit = () => { context.addCity(name, Math.ceil(Math.random() * 10)); setName(''); }; return ( <div className="add-city-form"> <input className="input" value={name} onChange={(e) => setName(e.target.value)} /> <button className="input" onClick={submit}>Add</button> </div> ); };
Now the app finally compiles! Try it out and see that it works… well, sort of. Our call to addCity
is giving a random number between 1 and 10. We’ll replace this with real data in just a bit.
But first, let’s make our app look nicer, because as professional software developers, we care about attention to detail. So replace the contents of App.css
file with the following CSS:
.city-overview { display: inline-grid; gap: 1em; padding: 2em; margin: 2em; background: #f7f7f7; box-shadow: 0px 2px 5px 1px #7777; } .city-overview h2 { margin-top: 0; } .city-overview .city-list { background: #fff; border-collapse: collapse; } .city-overview .city-list td, .city-overview .city-list th { border: 1px solid #aaa; padding: 0.5em 1em; } .city-overview .add-city-form { display: grid; grid-auto-flow: column; gap: 1em; } .city-overview .input { padding: 0.25em 0.5em; outline: none; border-radius: 3px; border: 1px solid #aaa; font: inherit; } .city-overview .input:focus { border-color: #17f; } .city-overview button.input { background-color: #17f; border-color: #17f; color: white; cursor: pointer; }
6. Using real data with OpenWeatherMap from RapidAPI
Now that we have a working app, let’s make it use real data.
There are many Weather APIs on RapidAPI.com. One that caught my attention was the OpenWeatherMap API:
Open Weather Map API Documentation
Get weather and weather forecasts for multiple cities.
That sounds exactly like the data we need. So we’ll use this.
Note: If you don’t have a RapidAPI account, make sure you sign up first before taking the following steps. Accounts are free, and start off with 100 API calls per day, which we’re not going to even come close to in this tutorial. (If you’re concerned that you might be getting close to the limit, check your RapidAPI dashboard.)
Trying out the API
Under the Code Snippets tab, you can choose what language to use. RapidAPI conveniently provides code snippets for fetch
, jQuery
, and XMLHttpRequest
:
fetch
: The newest and latest way to fetch data from APIs (hence the name). Because it’s so new, it isn’t supported in older browsers which may still be in use.jQuery
: This provides a convenient, cross-browser way to use APIs, but at the cost of including a dependency that adds to the load time of your page.XMLHttpRequest
: The oldest way to use APIs that works on practically all browsers, although not as convenient or easy to use.
For the sake of this tutorial, we’ll use fetch
, so make sure you’re using Chrome, Firefox, Safari, or another browser that supports fetch.
When you click this option, you’ll see something like this:
fetch("https://community-open-weather-map.p.rapidapi.com/weather?units=imperial&mode=json&q=London", { "method": "GET", "headers": { "x-rapidapi-host": "community-open-weather-map.p.rapidapi.com", "x-rapidapi-key": /* Your RapidAPI key will go here */ } }) .then(response => { console.log(response); }) .catch(err => { console.log(err); });
NOTE: You don’t need to go far to get your RapidAPI key, which is necessary for the next step! The RapidAPI page you’re on already provides that key in the example code snippet. Just make sure to copy and paste that into the next step!
If you try this example in your browser’s console, you will be able to inspect the data that it returns. As you tweak the query string parameters, you’ll get a feel for how to use this simple API.
Use the API to fetch real weather data
Let’s adapt this and use it in our AddCityButton
component, so that we get real weather data from OpenWeatherMap. We’ll replace the random-number generator with an API call, parse the JSON that it returns, and then call addCity
just like before.
Change the submit
function to look like this:
const submit = () => { const unit = 'imperial'; const mode = 'json'; const encodedName = encodeURIComponent(name); fetch(`https://community-open-weather-map.p.rapidapi.com/weather?units=${unit}&mode=${mode}&q=${encodedName}`, { "method": "GET", "headers": { "x-rapidapi-host": "community-open-weather-map.p.rapidapi.com", "x-rapidapi-key": /* Use your RapidAPI key here */ } }) .then(response => { console.log(response); if (response.status !== 200) throw new Error(); return response.json(); }).then(json => { console.log(json); context.addCity(name, json.main.temp); setName(''); }) .catch(err => { console.log(err); }); };
Note: Make sure you copy your RapidAPI key into this from the code snippet on the OpenWeatherMap API page’s code snippet.
This new function is straightforward:
- Create some URI-encoded parameters.
- Add them to the query string.
- Make the call using
fetch
. - Get the body as a JSON object.
- Get the temperature from
json.main.temp
.
Try the app. Now it really gives you aggregated weather data, and shows an accurate average!
How did React Context API help us?
If we didn’t use the React Context API, we would have needed to pass the state down to every component as props. In our example, it would have only been a slight annoyance to pass cities
and addCity
to the right components. But in real-world apps, many levels of intermediate components might need to be rendered between the root parent component and the target descendant components that need to access these data and functions.
Our React Context also gives us a central location for our data and the functions which act on our data. These can come from any combination of sources, including parent components, computed data, and even other APIs such as the many available on RapidAPIs.
In other words, React Context API provides a clean, simple, and centralized way to share data and functionality between ancestor components, sibling components, and descendant components.
With this new tool in your toolbox, you’ll be able to structure your app’s architecture even more effectively, avoiding the clutter and code debt that would have been created by passing your Context’s contents down through a complex hierarchy of props.
When should React Context API not be used?
The general rule of good software development is to keep the code and architecture as simple as is necessary for the given needs while avoiding code anti-patterns.
Because of that, state should be “lifted up” to the nearest common ancestor, and passed down through props.
When passing state or state-changing functions down as props would be too cumbersome, React Contexts might be the next simplest solution.
For more complex needs, the React Context API can be used in combination with Reducer Hooks, which are a slightly more advanced feature of React Hooks.
Taking the next steps
If you want to go deeper with learning and practicing the React Context API, here are a few suggestions:
- Try extending the application to delete cities from the list. Just like we have an
addCity
function inWeatherContext
, this will probably need adeleteCity
function. What does it take to add that function? What places in the code need to change? - Add a refresh button for each city. This will require a deeper structural change to the context. Each city might need a new property. How can this be integrated into the Context’s current data structure? How can code-duplication between this function and the
AddCityButton
component’ssubmit
function be reduced? - There’s been growing adoption of TypeScript in the React.js community, and for good reason: TypeScript allows us to visualize the structure of our data, and helps us with compile-time type checking. If you’re familiar with TypeScript syntax and semantics, how would you define the types needed for our React Context?
FAQ
What is the React Context API?
The React Context API is a new API built into recent versions of React, which allows developers to share data and functions between higher and lower levels in a Component Tree, without having to pass these data and functions as props through all the intermediate levels.
What is the purpose of React Context API?
The React Context API decouples producers of data and functions, higher in the Component Tree, from consumers of these data and functions, lower in the Component Tree. It also avoids the need to modify or wrap intermediate components, so that they do not need to change in order to allow developers to pass data and functions to lower levels in the Component Tree.
Does the React Context API replace Redux?
Redux is used for managing complex, nested state in single-page applications (SPAs). It allows developers to define state and actions that can modify that state of a Component Tree. The React Context API can replace the state management aspect of of Redux, because both of them have to do with managing complex, nested state without having to pass it down through the Component Tree as props. Along with Reducer Hooks API, the React Context API provides all the ingredients necessary to replace Redux in many usages.
What is a React Context Provider?
A React Context API has two concepts: a Provider and a Consumer. The Provider sits higher up in the Component Tree, and provides data and functions for lower portions of the Component Tree to access.
What is a React Context Consumer?
The Consumer is the inverse part of the React Context API, the part which accesses the data and functions provided by the Provider (see the above FAQ question).
Anon says
Great article! Very helpful, giving insight on more complex usages of Context than other articles.
Especially good job on covering wrapping React Hook setters into functions inside of context.