Tutorial

How To Manage User State with React Context

React

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.

Creative Commons License