Tutorial

How To Manage User State with React Context

Updated on March 23, 2021
Default avatar

By Mayuran Selvaraja

How To Manage User State with React Context

Introduction

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.

Prerequisites

To follow along with this article, you will need:

  • Familiarity with React fundamentals like nested Components, props, and state will be beneficial.

This tutorial was verified with Node v15.3.0, npm v6.14.9, and react v17.0.1.

Understanding the Problem

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.

Creating 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:

src/userContext.js
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.

Applying Context.Provider

Pass user state as value to context.Provider so it can be consumed by context.Consumer:

src/App.js
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.

Applying 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.

src/Main.js
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.

Updating Context from Nested Components

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.

src/App.js
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.

src/Main.js
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.

Conclusion

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.

Learn more about us


About the authors
Default avatar
Mayuran Selvaraja

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


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!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel