DEV Community

Cover image for How to Use React's Context Hook
codingcardio
codingcardio

Posted on

How to Use React's Context Hook

In this article, we'll take a look at how we can refactor a component to use React's Context hook. This lets us refactor our code quite a bit, and make things a lot more readable when using several contexts.

If you're already familiar with React's Context API, you know it can be a very powerful tool to pass data to multiple components. If you're not familiar let's look at an example. Say we have a Grandmother component that returns a Mother component like so:

import React, { createContext } from "react";

const FamilyContext = createContext({});
export const FamilyProvider = FamilyContext.Provider;
export const FamilyConsumer = FamilyContext.Consumer;

export class Grandmother extends React.Component {
  state = {
    familyName: { lastName: "Smith" }
  };

  render() {
    return (
      <FamilyProvider value={this.state.familyName}>
        <Mother />
      </FamilyProvider>
    );
  }
}

We wrap our <Mother /> component in a provider tag that will allow our children to consume some data. Now our components rendered from Grandmother will have access to some data. So let's say we had:

const Mother = () => {
  return <Child />;
};

const Child = () => {
  return (
    <FamilyConsumer>
      {familyContext => (
        <p>Last name: {familyContext.lastName}</p>
      )}
    </FamilyConsumer>
  );
};

Now we have access in the <Child /> from data being provided by the <Grandmother />

NOTE:

We use a state object value on Family Provider instead of passing an object directing to the value prop. If we were to pass a new object, this would cause all of our Consumers to re-render every time Provider re-renders.

Why use a context hook?

The above example works fine, but what if we had multiple contexts that we want to pass to our components? Building off of our example, that may look something like:

  ...

  render() {
    return (
      <FamilyProvider value={this.state.familyName}>
        <UserProvider value={this.state.user}>
          <NotificationsProvider value={this.state.notifications}>
            <Mother />
          </NotificationsProvider>
        </UserProvider>
      </FamilyProvider>
    );
  }

And in our consumers, we would have to write something like:

   ...
   return (

    <FamilyConsumer>
      {familyContext => (
        <UserConsumer>
          {currentUser => (
            <NotificationsConsumer>
              {notifications => (
                <div>
                  <p>User: {currentUser.email}</p>
                  <p>Last Name: {familyContext.lastName}</p>
                  <p>Notifications: {notifications.count}</p>
                </div>
              )}
            </NotificationsConsumer>
          )}
        </UserConsumer>
      )}
    </FamilyConsumer>

   )

It's starting to get a bit out of hand, as we can see this gets less readable with each context we add.

The how

Let's clean this up a little bit using some hooks.

First, let's make sure we're importing the useContext hook

import React, { useContext } from `react`;

Our useContext is going to accept a context object. So now let's refactor and see what that looks like! Let's first remove the Providers from our Grandmother

  // Grandmother
  ...
  render() {
    return <Mother />;
  }

No more nesting inside of multiple providers! This is now one less thing we have to worry about inside of this component

Now let's create a context object like so:

const FamilyContext = React.createContext({ lastName: "Smith" });

Ideally, you can keep these contexts in their own files and import them as you see fit. You can imagine they can get quite large depending on the data you're trying to store and pass.

Now we can refactor our <Child /> component and free it up from all that nesting:

const Child = () => {
  const familyContext = useContext(FamilyContext);
  const user = useContext(UserContext);
  const notifications = useContext(NotificationsContext);
  return (
         <div>
           <p>User: {currentUser.email}</p>
           <p>Last Name: {familyContext.lastName}</p>
           <p>Notifications: {notifications.count}</p>
         </div>
   );
};

See if you can create the Notifications and User contexts yourself based on what we've done so far!

Let's look at the child's return from non-hooks vs hooks difference:

Old:

   return (

    <FamilyConsumer>
      {familyContext => (
        <UserConsumer>
          {currentUser => (
            <NotificationsConsumer>
              {notifications => (
                <div>
                  <p>User: {currentUser.email}</p>
                  <p>Last Name: {familyContext.lastName}</p>
                  <p>Notifications: {notifications.count}</p>
                </div>
              )}
            </NotificationsConsumer>
          )}
        </UserConsumer>
      )}
    </FamilyConsumer>

   )

vs.

New:

  return (
         <div>
           <p>User: {currentUser.email}</p>
           <p>Last Name: {familyContext.lastName}</p>
           <p>Notifications: {notifications.count}</p>
         </div>
   );

We can see how much more readable this is. It's also a lot easier to understand the data flow this way. We can see exactly where each context is coming from instead of trying to following a nesting pattern.

I hope this helps!

Top comments (1)

Collapse
 
georgecoldham profile image
George

Thanks, been trying to wrap my head round this all day!