Manage User Login with React Context

Mayuran Selvaraja

It’s 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.

Here’s a simple example:

<Page user={user} avatarSize={avatarSize} />
// ... which renders ...
<PageLayout user={user} avatarSize={avatarSize} />
// ... which renders ...
<NavigationBar user={user} avatarSize={avatarSize} />
// ... which renders ...
<Link href={user.permalink}>
  <Avatar user={user} size={avatarSize} />
</Link>

In the above code, only the Avatar component actually uses the user prop, however each of its ancestor (parent, grandparent, etc) components receive user and pass it down. This means that if the Avatar component needed another prop in the future, we’d have to ensure that each of its ancestor components receive and pass it down. This makes our code difficult to maintain and error-prone.

In reality, the user state will need to be shared across many different components, therefore passing it as a prop will result it in nesting even deeper than the above example. 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. So if not Redux, then what?

React Context is an alternative solution to sharing data across components, without having to pass props down manually at every level. This post will explore the Context API and show how it can be used to manage user state.

🐊 Alligator.io recommends

Recommended React and GraphQL course

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. Let’s see what that means:

src/userContext.js

const userContext = React.createContext({user: {}}); // Create a context object

export {
  userContext // Export it so it can be used by other Components
};

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. In the example above we initialize userContext and provide defaultValue of {user: {}}.

Now that we have a Context object we can provide it with a value and subscribe to changes.

Context.Provider

src/App.js

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 (
      // Pass user state as value to context.Provider so it can be consumed by context.Consumer
      <userContext.Provider value={this.state.user}>
        <Main/>
      </userContext.Provider>
    );
  }
}

This is great to start with, wrapping the Main component with userContext.Provider ensures that the value we pass can be consumed within any of Main’s descendant child components. But how exactly do you consume?

Context.Consumer

Main.js

import {userContext} from './userContext';

function Main(props) {
  return (
    <Sidebar/>
    <userContext.Consumer>
      {(value) => (<Avatar user={value}/>);}
    </userContext.Consumer>
    <Content/>
  )
}

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. In this case it receives the App component’s state.user as value and renders an Avatar component.

Updating Context from Nested Components

Up to this point, we’ve used React Context to pass data to components that need it without having to manually pass props. Next, we need to be able to update the context from a nested child component. A logout button, for example.

In this case, you can pass a function down through the same context to allow consumers to update the context.

src/App.js

import {userContext} from './userContext';

class App extends React.Component {
  constructor(props) {
  super(props);
  this.state = {
    user: {}
  };

  this.logout = this.logout.bind(this);
  }

  // Add a logout method
  logout() {
    this.setState({user: {}});
  }

  componentDidMount() {
    // get and set currently logged in user to state
  }

  render() {
    // compose value prop as object with user object and logout method
    const value = {
      user: this.state.user,
      logoutUser: this.logout
    }
    return (
      <userContext.Provider value={value}>
        <Main/>
      </userContext.Provider>
    );
  }
}

The function that is passed to update context can be used in any nested component within the userContext.Provider component.

Main.js

import {userContext} from './userContext';

function Main(props) {
  return (
  <Sidebar/>
  <userContext.Consumer>
    {({user, logoutUser}) => {
      return (
        <Avatar user={user}/>
        <LogoutButton onClick={logoutUser}/>
      );
    }}
  </userContext.Consumer>
  <Content/>
  )
}

And that concludes a simple example of how to use React’s Context API to manage user state.

  Tweet It

🕵 Search Results

🔎 Searching...

Sponsored by #native_company# — Learn More
#native_title# #native_desc#
#native_cta#