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.
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:
When developing a React app, the primary situations where the React Context API really shines are:
There are three aspects to using React Contexts:
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.
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> ); };
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:
UserContext
object.AccountView
.AccountSummaryHeader
.We’re also using the useContext
React Hook. If you need a refresher on this, check out the official useContext
documentation.
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.
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:
Before continuing, make sure you have the following:
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.
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:
name
and temperature
key. Since we don’t start with any cities, this initial array is empty.cities
array. We give this a dummy function for now, since this initial value doesn’t have access to our root component’s state.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:
cities
and create a setter setCities
.addCity
function that just wraps the setCities
, which we’ll add into the Context.WeatherContext.Provider
.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.
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.
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.
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; }
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.)
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.
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:
fetch
.json.main.temp
.Try the app. Now it really gives you aggregated weather data, and shows an accurate average!
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.
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.
If you want to go deeper with learning and practicing the React Context API, here are a few suggestions:
addCity
function in WeatherContext
, this will probably need a deleteCity
function. What does it take to add that function? What places in the code need to change?AddCityButton
component’s submit
function be reduced?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.
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.
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.
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.
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).
We're thrilled to announce the latest update to the Rapid Enterprise API Hub (version 2024.3)!…
Are you curious about what your API consumers are searching for? Is your Hub effectively…
The RapidAPI team is excited to announce the February 2024 update (version 2024.2) for the…
This January's release brings exciting features and improvements designed to empower you and your developers.…
Rapid API is committed to providing its users with the best possible experience, and the…
In today's fast-paced digital world, APIs (Application Programming Interfaces) have become the backbone of modern…
View Comments
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.