Why You Should Be Using React Context

Why You Should Be Using React Context

Learn how to use the React Context API with the useContext Hook.

Introduction

As I started to get more comfortable with developing React applications, I stumbled across a frustrating problem in the applications I was building.

I wanted to be able to access information about the current signed-in user on multiple different pages and components.

To do that, I could either:

  • Write the code to get the current user many times over
  • Pass the user down as a prop to all the components that needed to use it, (and some parent props that didn't!)

I thought to myself, there must be a better way to do this, and I was right; enter the React Context API!

Video Lovers

For those who prefer to consume their content via video, I have also created a YouTube Guide to follow along with this blog :-) - Let me know if you come from Hashnode!

What is React Context

Context provides a way to share values between components, without having to explicitly pass a prop through every level of the tree. This avoids a problem known as prop drilling.

React Context is designed to share data that is considered "global"; like the current user, a UI theme, or local preferences; such as if the user selects dark mode or light mode.

Essentially, React Contexts allow you to store some value within a parent component, and every child inside that parent can access the value.

Why Do We Need React Context?

Let's look at an example of what life is like without React Context.

Let's say we have some logic to get the current user in our parent component, Page.

We also have a Link component that I'd like to access the user information in.

BUT the Link component lives within the NavBar component, and the NavBar component lives within the PageLayout component.

So our structure looks something like this:

-> Page
  -> PageLayout
    -> NavigationBar
      -> Link

In order for the Link to access the user, we will have to pass it down the hierarchy as props, like so:

<Page user={user}>
    <PageLayout user={user}>
        <NavigationBar user={user} >
            <Link href={user.permalink}/>
        </NavigationBar>
    </PageLayout >
</Page>

This is not only annoying to implement but the PageLayout and NavigationBar components may not even need the user; they only need it to pass it down the chain to the Link component.

Let's see how a React Context could help.

How To Create a React Context

The basic syntax of a React Context is:

// Create the context
const AuthContext = createContext({}); // {} here is the default value.

// A function that you would pass children components into.
// This function would return all the children passed into it, plus a context wrapped around it.
function AuthContextProvider({ children }) {
  return (
    <AuthContext.Provider value={/* Some value here*/}>
        {children}
    </AuthContext.Provider>;
}

value can be any value of your choosing. You can pass multiple values at once by passing in an object too :-)

How do I use the context?

Previously, (before React Hooks), you would have to use a Context.Consumer to read the value stored in the Context.Provider, which looked like this:

<AuthContext.Consumer>
  {value => /* render something based on the context value */}
</AuthContext.Consumer>

But, since we live in the future™, we can use the more simple useContext Hook

The useContext hook accepts the context object itself as an argument and returns the current context value, which is the current value of the value prop of the context.

The code to initialize that hook looks like this:

const useUser = () => useContext(AuthContext)

In the real world, you would likely export this hook, and in any functional component that you want to get the value of the context, you can simply import it and use it, like so:

import  useUser  from '../context/userContext'

and then:

// myUser contains the value of the current value in the context.
const myUser = useUser()

No need for the Consumer anymore!

How Does It Work?

Whenever the Context Provider value updates, the useContext hook triggers a re-render with the latest context value that it has to all the subscribers of the context.

A component calling useContext will always re-render when the context value changes.

Real-World Examples

AWS Amplify Example

The example context I have hinted at in the above code snippets is to have the current signed-in user available within the context.

Let's explore how we can achieve that, with an example userContext using AWS Amplify.

AuthContext.tsx:

import React, { ReactElement, useState, useEffect } from 'react';
import { createContext, useContext } from 'react';
import { Auth, Hub } from 'aws-amplify';

// initialize the context with an empty object
const UserContext = createContext({});

export default function AuthContext({ children }) {
  // Store the user in a state variable
  const [user, setUser] = useState(null);
  useEffect(() => {
    checkUser();
  }, []);

  // (Only once) - when the component mounts, create a listener for any auth events.
  useEffect(() => {
    Hub.listen('auth', () => {
      // Hub listens for auth events that happen.
      // i.e. Sign in, sign out, sign up, etc.
      // Whenever an event gets detected, run the checkUser function
      checkUser();
    });
  }, []);

  async function checkUser() {
    try {
      // Get the current authenticated user
      const user = await Auth.currentAuthenticatedUser();
      if (user) {
        // set the user in state
        setUser(user);
      }
    } 
    // Error occurs if there is no user signed in.
    catch (error) {
      // set the user to null if there is no user. 
      setUser(null);
    }
  }

  return (
    <UserContext.Provider
      value={{
        user, // the value of the current user
        setUser, // the setState method - allows us to set the current user in state from anywhere in our application
      }}
    >
      {children}
    </UserContext.Provider>
  );
}

// export the hook so we can use it in other components.
export const useUser = () => useContext(UserContext);

In my _app.js file, (which in Next.JS, wraps all pages of the application):

import React, { useEffect } from 'react';
import AuthContext from '../context/AuthContext';

function MyApp({ Component, pageProps }) {
 return (
      <AuthContext>
          <Component {...pageProps} />
      </AuthContext>
  );
}

export default MyApp;

Now, I can access the current signed-in user from any page in my application, with:

// De-structure user out of the value prop returned by useUser hook.
const { user } = useUser();

Conclusion

React Context and the useContext hook is a powerful tool to manage a piece of data that:

  • May change.
  • Needs to be used by multiple components.

A helpful use case of React Context is to provide the current user to all the components of the application. I hope this helps you to understand what the React Context API is and how to use it in the modern age with hooks.

Thanks for reading :-)!

Connect With Me!

Buy me a coffee ☕

YouTube: youtube.com/channel/UCJae_agpt9S3qwWNED0KHcQ

Twitter: twitter.com/JarrodWattsDev

GitHub: github.com/jarrodwatts

LinkedIn: linkedin.com/in/jarrodwatts

Website: jarrodwatts.com

Did you find this article valuable?

Support Jarrod Watts by becoming a sponsor. Any amount is appreciated!