Read a discussion of the React Context API, accompanied by working examples to demonstrate its functionality. See how it solves the problem of "prop drilling", making React development more elegant.
In this tutorial, we will explore React Context API in details.
As with most component-based frontend frameworks, passing some form of data from one component to another is usually a real need. Typically it comes in the form of passing data from a parent to a child component or even child to parent components. This leads to components having data they don’t actually need, but they need to pass on down the tree.
This gets cumbersome pretty fast especially for certain types of props (e.g locale preference, UI themes, language settings etc) that are required by many different components within an application. The Context API aims to solve this problem, and provides a way to share data values like this between components without having to pass a prop through every level of the app tree.
Before we dive into the context API, to ensure you can easily follow along it’s important you have the following:
The Context API is a component structure provided by the React framework, which enables us to share specific forms of data across all levels of the application. It’s aimed at solving the problem of prop drilling.
“Prop drilling (also called “threading”) refers to the process you have to go through to get data to parts of the React Component tree.” – Kent C. Dodds.
Before the Context API, we could use a module to solve this, which led to the increasing popularity of state management libraries like Redux. Libraries like Redux allows you to get data from the store easily, anywhere in the tree. However, let’s focus on the Context API.
Related: The what and why of Redux.
The Context API has actually always been there but was considered experimental. Moving forward the API was improved to stability, and as of the release of version 16.3, the feature was made available and subsequently considered a new addition to the clan of features that make React a wonderful framework.
Before now many of the tools we have been used to like react-redux
, react-native-flux
, MobX-react
, and react-router
all used context to function, so you were probably already using and loving it, even if not directly. So let’s learn how best to use it.
As we mentioned earlier, the Context API is useful for sharing data that can be considered global, such as the currently authenticated user, the theme settings for the application, and more. In situations where we have these types of data, we can use the Context API and we don’t necessarily have to use extra modules.
In fact, any situation where you have to pass a prop through a component so it reaches another component somewhere down the tree is where you can use the Context API.
So how do we use it?
With the new Context API, several new functions have been added to the mix creating giving us a rich tool to work with. If you’ve previously attempted to use the old (experimental, do not use) Context API, you may have come across some methods. However, let’s look at some code snippets on how we can use the Context API.
In this example, we will create a simple instance using the Context API.
You can use create-react-app to create a sample project to work with:
1$ npx create-react-app sample
In the src
directory, you can now play along with the tutorial. Let’s start by modifying or adding a few files.
Create a language.js
file with the following content:
1const lang = { 2 esp:{ 3 open: 'abierto', 4 close: 'cerca', 5 changeLanguage: 'haga clic para cambiar su idioma al inglés', 6 header: 'Mira tu idioma', 7 text: 'Haga clic en el botón de abajo para cambiar su idioma' 8 }, 9 en: { 10 open: 'open', 11 close: 'close', 12 changeLanguage: 'click to switch to spanish', 13 header: 'Watch your language', 14 text: 'click on the button below to change your language' 15 } 16 } 17 18 export default lang;
Create a context.js
file in the src
directory with the following content:
1import React from 'react' 2 import lan from './language' 3 4 const LanguageContext = React.createContext({ 5 lang: lan.esp, 6 toggleLanguage: () => { } 7 }); 8 9 export default LanguageContext;
Replace the App.js
with the following content:
1import React, { Component } from 'react'; 2 import './App.css'; 3 import LanguageContext from './context' 4 import lan from './language' 5 6 class App extends React.Component { 7 render() { 8 return ( 9 <LanguageContext.Provider value={{lang: lan.en}}> 10 <Toolbar /> 11 </LanguageContext.Provider> 12 ); 13 } 14 } 15 16 function Toolbar(props) { 17 return ( 18 <div> 19 <CloseButton /> 20 </div> 21 ); 22 } 23 24 function CloseButton(props) { 25 return ( 26 <LanguageContext.Consumer> 27 {context => <Button {...props} text={context.lang.close} />} 28 </LanguageContext.Consumer> 29 ); 30 } 31 32 function Button(props) { 33 return ( 34 <button>{props.text}</button> 35 ); 36 }
Let’s have a good look at this, first, we set up a context instance using the createContext
function in the React
object. This function takes in the default values of the context we wish to create. it’s the starting point of the Context API with this you create the provider and consumer pair.
1const {Provider, Consumer} = React.createContext(default);
To use this object returned we set up the provider component in the parent component, you can also pass in your prop/data to change the default values.
This component wraps the intended parent component and its values are made available to the child component via the corresponding consumer component.
1import lan from './language' 2 3 <LanguageContext.Provider value={{lang: lan['en']}}> 4 <Toolbar /> 5 </LanguageContext.Provider>
In this example, the provider wraps the toolbar component in the App component declaration. therefore allowing the data to be available for use.
Down the tree, in the CloseButton component the context values are being passed to a Button component to create a CloseButton in the currently set language (a button that uses the theme, got it?)
1function CloseButton(props) { 2 return ( 3 <LanguageContext.Consumer> 4 {context => <Button {...props} text={context.lang.close} />} 5 </LanguageContext.Consumer> 6 ); 7 }
Now we can assess the values of the context in any child element using the LanguageContext.Consumer
wrapper followed by the arrow function representation to retrieve the data.
Sometimes we want to change some data in the context, for example changing the theme of the app when a user changes their theme. To do this, we simply connect the data in the context Provider to the state of the parent element and then change this parent’s state using functions in props, a better way is to have the function that will change the context value passed down in the context itself.
Let’s demonstrate how this will work. In your create-react-app
project, make sure the content of the context.js
is the same as the following:
1import React from 'react' 2 import lan from './language' 3 4 const LanguageContext = React.createContext({ 5 lang: lan.esp, 6 toggleLanguage: () => { } 7 }); 8 9 export default LanguageContext;
Next, create a LanguageTogglerButton.js
file. This will be a button that toggles the theme. Paste the following in the file:
1import React from 'react'; 2 import LanguageContext from './context'; 3 4 function LanguageTogglerButton() { 5 return ( 6 <LanguageContext.Consumer> 7 {({ lang, toggleLanguage }) => ( 8 <React.Fragment> 9 <header className="App-header"> 10 <h1 className="App-title">{lang.header}</h1> 11 <p>{lang.text}</p> 12 </header> 13 <Toolbar /> 14 </React.Fragment> 15 )} 16 </LanguageContext.Consumer> 17 ); 18 } 19 20 function Toolbar(props) { 21 return ( 22 <div> 23 <CloseButton /> 24 </div> 25 ); 26 } 27 28 function CloseButton(props) { 29 return ( 30 <LanguageContext.Consumer> 31 {({ lang, toggleLanguage }) => ( 32 <Button 33 onClick={toggleLanguage} 34 text={lang.changeLanguage}> 35 </Button> 36 )} 37 </LanguageContext.Consumer> 38 ); 39 } 40 41 function Button(props) { 42 return ( 43 <button className="App-button" onClick={props.onClick}>{props.text}</button> 44 ); 45 } 46 47 export default LanguageTogglerButton;
Lastly, in App.js
you can use like this:
1import React from 'react'; 2 import './App.css'; 3 import LanguageContext from './context'; 4 import LanguageTogglerButton from './LanguageTogglerButton'; 5 import lang from './language'; 6 7 class App extends React.Component { 8 constructor(props) { 9 super(props); 10 this.toggleLanguage = () => { 11 this.setState(state => ({ 12 lang: 13 state.lang === lang['en'] 14 ? lang['esp'] 15 : lang['en'], 16 })); 17 }; 18 this.state = { 19 lang: lang['esp'], 20 toggleLanguage: this.toggleLanguage 21 }; 22 } 23 24 render() { 25 return ( 26 <div> 27 <LanguageContext.Provider value={this.state}> 28 <LanguageTogglerButton /> 29 </LanguageContext.Provider> 30 </div> 31 ); 32 } 33 } 34 35 export default App;
In this process, we are able to change the context via a nested component by sending the function to change the component as part of the state, which is being sent to the provider.
The Context API makes it easier to have our global and app-wide data available to all components therefore, making it easier and more accessible. The Context API has been considered as one of the key features of the react framework and a prominent improvement in the recent releases of the framework.
There are sure to be more improvements and new features from the framework and its development community. In the meantime, enjoy what wonderful features it has like the Context API.
Here’s a link to the GitHub repo for the article.