It is common in React for data to flow from top-to-bottom through props (parent component to children components), but this is not ideal all the time. There may be situations where you have data that needs to be available to many nested components at varying depths and passing props becomes difficult to maintain and error-prone.
In this situation, it may seem like a good idea to use Redux to manage state, but many argue it should not be your first option.
React Context is an alternative solution to sharing data across components, without having to pass props down manually at every level.
In this article, you will explore the Context API and learn how it can be used to manage user state.
To follow along with this article, you will need:
This tutorial was verified with Node v15.3.0, npm
v6.14.9, and react
v17.0.1.
Here’s an example of a Page
component passing a user
and avatarSize
prop:
<Page user={user} avatarSize={avatarSize} />
Which renders a PageLayout
component:
<PageLayout user={user} avatarSize={avatarSize} />
Which renders a NavigationBar
component:
<NavigationBar user={user} avatarSize={avatarSize} />
Which renders a Link
and Avatar
that uses the user
and avatarSize
props:
<Link href={user.permalink}>
<Avatar user={user} size={avatarSize} />
</Link>
In the above example, only the Avatar
component actually uses the user
prop. However each of its ancestor components (parent, grandparent, etc.) receives user
and passes it down. This means that if the Avatar
component needed another prop in the future, you would have to ensure that each of its ancestor components receives and passes it down.
In reality, the user
state will need to be shared across many different components, therefore passing it as a prop will result in it in nesting even deeper than the above example.
React.createContext
The React.createContext
method returns a Context
object. This Context
object comes with two important React components that allow for subscribing to data: Provider
and Consumer
.
When React renders a component that subscribes to this Context
object it will read the current context value from the closest matching Provider
component above it in the tree.
The createContext
method takes in an optional defaultValue
argument, which is provided to Context.Consumer
if a matching Context.Provider
component could not be found in the tree.
Create a Context
object and export it to be used by other components:
import React from 'react';
const userContext = React.createContext({user: {}});
export { userContext };
In the example above, you initialized userContext
and provided defaultValue
of {user: {}}
.
Now that you have a Context
object, you can provide it with a value and subscribe to changes.
Context.Provider
Pass user state as value to context.Provider
so it can be consumed by context.Consumer
:
import React from 'react';
import Main from './Main';
import {userContext} from './userContext';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
user: {}
};
}
componentDidMount() {
// get and set currently logged in user to state
}
render() {
return (
<userContext.Provider value={this.state.user}>
<Main/>
</userContext.Provider>
);
}
}
export default App;
This is great to start with, wrapping the Main
component with userContext.Provider
ensures that the value you pass can be consumed within any of Main
’s descendant child components.
Context.Consumer
userContext.Consumer
takes in a function as a child. This function receives the current context value (value that is passed as a prop to userContext.Provider
) and returns a React node.
import Sidebar from './Sidebar';
import Content from './Content';
import Avatar from './Avatar';
import {userContext} from './userContext';
function Main(props) {
return (
<Sidebar/>
<userContext.Consumer>
{({value}) => {
<Avatar value={value}/>
}}
</userContext.Consumer>
<Content/>
)
}
export default Main;
In this case, it receives the App
component’s state.user
as a value and renders an Avatar
component.
Up to this point, you have used React Context to pass data to components that need it without having to manually pass props. Next, you will need to be able to update the context from a nested child component.
Consider a button for logging a user out.
In this case, you can pass a function down through the same context to allow consumers to update the context.
import React from 'react';
import Main from './Main';
import {userContext} from './userContext';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
user: {}
};
this.logout = this.logout.bind(this);
}
logout() {
this.setState({user: {}});
}
componentDidMount() {
// get and set currently logged in user to state
}
render() {
const value = {
user: this.state.user,
logoutUser: this.logout
}
return (
<userContext.Provider value={value}>
<Main/>
</userContext.Provider>
);
}
}
export default App;
The function that is passed to update context can be used in any nested component within the userContext.Provider
component.
import Sidebar from './Sidebar';
import Content from './Content';
import Avatar from './Avatar';
import LogoutButton from './LogoutButton';
import {userContext} from './userContext';
function Main(props) {
return (
<Sidebar/>
<userContext.Consumer>
{({user, logoutUser}) => {
return (
<Avatar user={user}/>
<LogoutButton onClick={logoutUser}/>
);
}}
</userContext.Consumer>
<Content/>
)
}
export default Main;
The logout button has been added to the Main
component.
And that concludes an example of how to use React’s Context API to manage user state.
In this article, you were introduced to React Context and using Context.Producer
and Context.Consumer
.
If you’d like to learn more about React, take a look at our How To Code in React.js series, or check out our React topic page for exercises and programming projects.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!