Authentication with React Context

4 minute read

Most react apps require the concept of authentication and storing user information. In these scenarios, you might end up with various components in the app that all need access to the same user information like the avatar or display name. On top of this, a few of our components might want to update this information and make it be instantly available everywhere else.

Some go-to solutions for this use case could be:

  • Add a package like redux to your project and add bloat for a simple use case.

  • Pass the information using props all the way down the tree, and create “props hell” for yourself.

  • Use the observer pattern by using something like PubSub JS.

As of new React versions, there is a better way to accomplish this by using React Context (https://reactjs.org/docs/context.html). In this post, we will run through an example of how React Context can be used for this specific case. So let’s get right into it.

Consider the following sample piece of code. We have a dashboard app that has two components somewhere in our tree: NavBar and UserSettings. Both these components need the user object and we can pass it through the props. This snippet is the extremely basic use case, but imagine having components nested deep in the tree and the pain of passing the prop all the way down.

This example and more complicated versions of it can be handled well by utilizing react context to store the user information, and all the components within the app can then use it on a need-to-use basis. It’s time to run through a step by step example.

Setting up a User Context component

First of all, we will setup the react context. To keep our code clean, we will define a provider and consumer in the same component file. Create a new folder for context and create a new file /src/context/UserContext.jsx.

Code Break Down 🕺

In the code above we define a new user context by using the createContext helper and specify that it will contain a user object and a updateUser method that will be used to update the user data.

We then define a UserProvider class that wraps any children in the context provider and keeps the required object in the state.

    // This provider will wrap the rest of the tree and we pass in the // user in the state and the updateUser function as well. 
    <UserContext.Provider
      value={this.state}
    >
       {children}
    </UserContext.Provider>

At the end of the file, we also export the Consumer in the same file.

    export const UserConsumer = UserContext.Consumer;

Now that we have a UserProvider component we can run through a practical example of how it can be used in our application. To add a little complexity to our pretend example let’s assume the TopNavBar and UserSettings components are deeper in the tree and not in the App component.

Setting up the initial value of a user in the context

We need to set up the initial value of the logged-in user in our main App.jsx component. This way as soon as a user logs in, the context is filled with the required user data.

Code Break Down 🕺

In the above code, our app component makes sure it pulls the logged in user’s information from the server and stores it in the state. Then the rest of our code gets wrapped in the UserProvider that we created earlier. Note that we are passing the UserProvider with the user prop to set up the value of the user in the context.

    <UserProvider user={user}>

Use the context in any component

Now that our app component wraps everything in the UserProvider component and setups up the initial value of our user, we can go ahead and use this in our components. Let’s circle back to the TopNavBar component and implement this.

Code Break Down 🕺

Alright, we are almost at the finish line. In this component, you can see that we are loading the UserConsumer from our component we created earlier.

    import { UserConsumer } from ../contexts/UserContext;

IMPORTANT NOTE: In order to use the context in our component we need to wrap our component in the UserConsumer. I like to pass in the context as a prop to be used in the component. That’s what this next code snippet right here does. We wrap the component in the UserConsumer and pass the state to our TopNavBar component.

    const withContext = () => (
       <UserConsumer> 
         {state => <TopNavBar context={state} />} 
       </UserConsumer>
    );

    export default withContext;

Within the TopNavBar component, we have direct access to all the user info. We read it in our constructor and set it in the state to be read again.

    // Get the user from our context
    const { context } = this.props;
    const { user } = context;

    this.state = { user };

Updating new user info in the context

Now you might ask, what about updating the user information when the user uploads a new picture? Don’t worry about it, I got you. Taking the TopNavBar component again we can access the function passed into the context. Just like we got the user info we can access the update function from context and call it to update the info.

    // Get the user from our context 
    const { context } = this.props; 
    const { updateuser } = context;

    updateUser(newUserInfo)

Summary

In this post, we took a look at how React Context can be used to store global information that needs to be accessible by multiple components. It helps us write cleaner code without props flowing in all possible directions.

I hope this was helpful, and if you have any comments feel free to reach out.

Updated: