ReactT2 Dark_1200x303

Learn how the Context API works in React and the best times to use it to avoid prop-drilling in your application.

One of the best things about React is that we have a lot of different ways to solve specific problems. We have a few different form libraries, a bunch of CSS libraries and, for the most important part of React, we have a lot of different libraries specific to state data problems in React.

Identifying when to use a certain library in our project is a skill that we develop through experience. Especially in React, where we have so many libraries to choose from, sometimes we might end up installing and using libraries that we don’t need.

Context API is a React API that can solve a lot of problems that modern applications face related to state management and how they’re passing state to their components. Instead of installing a state management library in your project that will eventually cost your project performance and increase your bundle size, you can easily go with Context API and be fine with it.

Let’s understand what the Context API is, the problems it solves and how to work with it.

Why Context API?

One of the concepts of React is to break your application into components, for reusability purposes. So in a simple React application, we have a few different components. As our application grows, these components can become huge and unmaintainable, so we break them into smaller components.

That’s one of the best concepts about React—you can create a bunch of components and have a fully maintainable and concise application, without having to create a super huge component to deal with your whole application.

After breaking components into smaller components for maintainability purposes, these small components might now need some data to work properly. If these small components need data to work with, you will have to pass data through props from the parent component to the child component. This is where we can slow down our application and cause development issues.

Let’s imagine that we have a component called Notes that is responsible to render a bunch of notes.

const Notes = () => {
  const [notes] = useState([
    {
      title: "First note",
      description: "This is my first note",
      done: false
    }
  ]);
  return (
    <div>
    <h1>Notes</h1>
      {notes.map(note => {
        return (
        <div>
          <h1>{note.title}</h1>
          <h3>{note.description}</h3>
          <p>{note.done ? "done!" : "not done!"}</p>
        </div>
        );
      })}
    </div>
  );
};

Just looking at this code, we can notice that we can break this component into smaller components, making our code cleaner and more maintainable. For example, we could create a component called Note and inside that component, we would have three more components: Title, Description and Done.

const Notes = () => {
  const [notes] = useState([
    {
      title: "First note",
      description: "This is my first note",
      done: false
    }
  ]);
  return (
    <div>
      <h1>Notes</h1>
      {notes.map(({ title, description, done }) => {
        return <Note title={title} description={description} done={done} />;
      })}
    </div>
  );
};

const Note = ({ title, description, done }) => {
  return (
    <div>
      <Title title={title} />
      <Description description={description} />
      <Done done={done} />
    </div>
  );
};

const Title = ({ title }) => {
  return <h1>{title}</h1>;
};

const Description = ({ description }) => {
  return <h3>{description}</h3>;
};

const Description = ({ description }) => {
  return <h3>{description}</h3>;
};

We now have a few components, and we certainly increased the reusability and maintainability of our example application. But, in the future, if this application grows in size and we feel the need to break these components into smaller components, we might have a problem.

Passing data through props over and over can cause problems for your application. Sometimes you might pass more props than you need or even forget to pass props that you do need, rename props through the components without noticing, etc. If you’re passing data through props from the parent component to a fourth- or fifth-level component, you’re not reusing and writing maintainable code, and this might prejudice your application in the future.

This is what we call “prop-drilling.” This can frustrate and slow down your development in the mid- to long-term—passing props over and over again to your components will cause future problems in your application.

That’s one of the main problems that Context API came to solve for us.

Context API

The Context API can be used to share data with multiple components, without having to pass data through props manually. For example, some use cases the Context API is ideal for: theming, user language, authentication, etc.

createContext

To start with the Context API, the first thing we need to do is create a context using the createContext function from React.

const NotesContext = createContext([]);

The createContext function accepts an initial value, but this initial value is not required.

After creating your context, that context now has two React components that are going to be used: Provider and Consumer.

Provider

The Provider component is going to be used to wrap the components that are going to have access to our context.

<NotesContext.Provider value={this.state.notes}>
...
</Notes.Provider>

The Provider component receives a prop called value, which can be accessed from all the components that are wrapped inside Provider, and it will be responsible to grant access to the context data.

Consumer

After you wrap all the components that are going to need access to the context with the Provider component, you need to tell which component is going to consume that data.

The Consumer component allows a React component to subscribe to the context changes. The component makes the data available using a render prop.

<NotesContext.Consumer>
  {values => <h1>{value</h1>}
</Notes.Consumer>

useContext

You might have been using React Hooks for some time now, but if you don’t know yet what React Hooks are and how they work, let me very briefly explain them to you:

React Hooks allow us to manage state data inside functional components; now we don’t need to create class components just to manage state data.

React has a few built-in hooks such as useState, useCallback, useEffect, etc. But the one that we’re going to talk and learn more about here is the useContext hook.

The useContext hook allows us to connect and consume a context. The useContext hook receives a single argument, which is the context that you want to have access to.

const notes = useContext(NotesContext);

The useContext is way better and cleaner than the Consumer component—we can easily understand what’s going on and increase the maintainability of our application.

Let’s now create an example with the Context API and the hook to see how it applies in a real-world application. We’re going to create a simple application to check if the user is authenticated or not.

We’ll create a file called context.js. Inside that file, we’re going to create our context and our provider, import the useState and useContext hooks from React, and create our context which is going to be called AuthContext. The initial value of our AuthContext will be undefined for now.

import React, { useState, useContext } from "react";
const AuthContext = React.createContext(undefined);

Now, we’re going to create a functional component called AuthProvider, which will receive children as props. Inside this component, we’re going to render more components and handle the state data that we want to share with the other components.

const AuthProvider = ({ children }) => {
...
};

First, we’ll create our auth state. This will be a simple boolean state to check if the user is authenticated or not. Also, we’re going to create a function called handleAuth, which will be responsible to change our auth state.

const [auth, setAuth] = useState(false);
const handleAuth = () => {
  setAuth(!auth);
};

The Provider does not accept array values, so we’re going to create an array called data, which will contain our auth state and our handleAuth function. We’re going to pass this data as our value in our AuthContextProvider.

const AuthProvider = ({ children }) => {
  const [auth, setAuth] = useState(false);
  const handleAuth = () => {
    setAuth(!auth);
  };
  const data = [auth, handleAuth];
  return <AuthContext.Provider value={data}>{children}  </AuthContext.Provider>;
};

Now, inside our context.js file, we’ll also create a simple hook component called useAuth, which we’ll use to consume our context. If we try to use this component outside our Provider, it will throw an error.

const useAuth = () => {
  const context = useContext(AuthContext);
  if (context === undefined) {
    throw new Error("useAuth can only be used inside AuthProvider");
  }
  return context;
};

Then we’re going to export our AuthProvider and useAuth at the end of our file.

Now, in our index.js component, we need to import the AuthProvider component and wrap the components that we want to give access to the context inside this provider.

import { AuthProvider } from "./context";
ReactDOM.render(
  <React.StrictMode>
  <AuthProvider>
  <App />
  </AuthProvider>
  </React.StrictMode>,
  rootElement
);

Next, inside our App.js file, we’re going to manage our context data. We need first to import the useAuth hook that we created and get the auth and handleAuth from useAuth.

Let’s create a button and, every time we click this button, we’ll invoke the handleAuth function. Let’s also use a ternary rendering of a simple h1 to check if the auth value is changing as we click the button.

import { useAuth } from "./context";
const App = () => {
  const [auth, handleAuth] = useAuth(useAuth);
  return (
    <div>
      <h3>Is authenticated?</h3>
      <h1>{auth === false ? "Not authenticated!" : "Authenticated!"}  </h1>
      <button onClick={handleAuth}>Change auth</button>
    </div>
  );
};

We now have a simple application using the Context API. Notice that we don’t need to pass any props from parent component to child components.

The Context API can be really helpful in some use cases, such as authentication when you need to check if the user is authenticated in a few unrelated components.

Conclusion

In this article, we learned more about the React Context API. The Context API came to solve a few different problems that we were having in React applications—one of the most important is prop-drilling. We created an example using the Context API in a class component, then in a functional component. Also, we were introduced to how to use the useContext hook.


Leonardo Maldonado
About the Author

Leonardo Maldonado

Leonardo is a full-stack developer, working with everything React-related, and loves to write about React and GraphQL to help developers. He also created the 33 JavaScript Concepts.

Related Posts

Comments

Comments are disabled in preview mode.