React Context API is an advanced React feature that will make your React code easier to follow (and more performant 🚀!) in certain scenarios where you need to share a value with every component in your app.

The perfect example is a dark mode toggle.

Every component in your app needs to know the value of theme so it can apply the appropriate styles.

The way you would ordinarily pass a value around with React is using props, but imagine how messy that could get when you have components many levels deep:

Drilling props deep like this isn't just hard to follow.

It complicates every component and can even cause performance issues due to components re-rendering too often. This can easily happen when prop drilling down through a component tree. If a prop at the top level changes, it will cause all the components down the tree to re-render. This causes issues especially if any of these components perform expensive calculations.

There has to be a cleaner and more optimised way to make values every component needs accessible globally!

It's called the React Context API and in this post, you will when and how to use Context through examples.

We'll implement a theme toggle two ways - first, by drilling props. Then, using Context.

You'll see for yourself how much cleaner Context makes your code in these scenarios.

Does Context replace the need for props?
Not at all. Props should still be your go-to for passing data to components. Consider Context when some data needs to be accessible by many components at different nesting levels

What is the React Context API?

The React Context API allows us to pass data to components without using props. Normally, React has a one way data flow. It passes data from parent to child component. If you want to get a piece of data to a deeply nested component, you need to pass it down via the props through all the parent components on the way.

Example of prop drilling

Let’s look at an example of a light/dark mode using prop drilling. We’ll create an App component with a Content component containing a button. This button will toggle the whole app from light to dark mode.

const App = () => {
 const [theme, setTheme] = React.useState("light");

 const toggleTheme = () => {
   setTheme(theme === "light" ? "dark" : "light");
 };

 return (
   <Content theme={theme} toggleTheme={toggleTheme}>
     <ThemeToggler theme={theme} toggleTheme={toggleTheme} />
   </Content>
 );
};

In this example, the theme value and toggleTheme function is passed down from the App component to the Content and ThemeToggler components.

This is an example of prop drilling. The functions are being passed through multiple components in order to be accessed by child components.

This seems simple enough. You're probably wondering why on earth you would use Context when you can do the same thing with props? Well, in this example, you would be completely right! But imagine the component that needs the prop is 5 levels deep. Imagine our app is a big store that needs the theme passed down to every button, header and page many levels deep. Let's build on this example and show where Context really shines!

Example with React Context

Let’s take a look at this same example using React Context.

const ThemeContext = React.createContext("light");

const ThemeProvider = ({ children }) => {
 const [theme, setTheme] = React.useState("light");

 const toggleTheme = () => {
   setTheme(theme === "light" ? "dark" : "light");
 };

 return (
   <ThemeContext.Provider value={{ theme, toggleTheme }}>
     {children}
   </ThemeContext.Provider>
 );
};

First we create our ThemeContext and set it a default value of "light". We only have to define this once and then we can access it in any component with the help of the useContext hook.

Now our App looks like this:

const App = () => {
  return (
    <ThemeProvider>
      <Content>
        <ThemeToggler />
      </Content>
    </ThemeProvider>
  );
};

You can see there are no props passed. Instead our content is wrapped with our ThemeProvider. This provides all children components within our ThemeProvider wrapper access to our context values theme and toggleTheme.

Let’s see how we access our theme by looking at the ThemeToggler component:

const ThemeToggler = () => {
  const { theme, toggleTheme } = React.useContext(ThemeContext);

  return (
    <button
      className={`${theme === "light" ? "darkMode" : "lightMode"}`}
      onClick={toggleTheme}
    >
      Toggle {theme === "light" ? "Dark" : "Light"} Mode
    </button>
  );
};

In our ThemeToggler, we access our theme and toggleTheme function with the useContext hook. This info is being passed to a button. The button will toggle our theme back and forth between light and dark mode.

As long as the component is within ThemeProvider, we can use useContext to access our current theme value. This saves us from passing the theme prop down from App through the whole component tree to where we need it. In this case that’s just AppThemeToggler.

Prop Drilling vs React Context API

In a complex application with many components needing this theme info, it could get messy 😅.

Let’s take a look at a longer example where our theme needs to be passed down through many layers of components using prop drilling.

Let’s say AuthorAvatar gets a different color border depending if we’re in light and dark mode. In order to access our theme to style the border, we need to pass the theme props like so:

App → Content → Blog → BlogPost → AuthorWidget → AuthorAvatar

Here’s our prop drilling example in code, make note how the theme prop is being passed through the components:

In this case our Blog, BlogPost and AuthorWidget components aren’t even using the theme! Same with our Header component passing props to ThemeToggler. In the first example, we’re passing the theme data ALL the way down from App, through the component tree to access it in AuthorAvatar. This isn’t even considering performance and potentially costly re-renders 🤯.

Let’s look at this same example with React Context for comparison. In this case we set the theme and toggle it in our ThemeProvider. Then we only access it in the components that need it.

Here’s our same example of React Context in code:

You see we’re not passing theme or toggleTheme to the components that are not using it. Instead, we are using the useContext hook to access our theme only from the components that need it! In this case, Content and AuthorAvatar.

This is a simplified example, but you can imagine in an application with many nested components, where unrelated components all over the component tree need access to a theme, context can be useful.

Let’s summarize further when exactly is a good use case for the React Context API.

When to use the React Context API

So when should you use the react context api vs prop drilling? The React Context API is especially useful when:

  • You have data or functions that need to be accessed by many components in the tree. Specifically, they should have all different parent components rather than one common ancestor.
  • You have props that are deeply nested in the component tree. If props are being passed down through many levels, it can make the code difficult to read and maintain.
  • You want to avoid re-rendering components that are not directly consuming the context data or functions.

Accessing data with the useContext hook

In our example above, we access data in our context via the useContext hook. We can use the useContext hook to grab data from our context for any component within our ThemeProvider.

The useContext hook accepts a context object (the value returned from React.createContext) and returns the current context value for that context. This value is determined by the value prop of the nearest <MyContext.Provider> above the component calling useContext in the tree.

You can see how useContext can decrease the amount of code and makes things easier if our component tree is very large. If you need to access something in a deeply nested component, that can be a lot of prop drilling!

Core principles of the React Context API

TL;DR? Let’s break down the core principles of the Context API. If you take anything away from this article, let it be this!

  1. Context is a way to pass data through the component tree without having to pass props down manually from parent to child component.
  2. Context is” for data that’s considered “global" in the application. For example, the current authenticated user, theme, or preferred language.
  3. Context should only be used for data that’s relevant to multiple components with different parent components.
  4. Context is updated using a Provider component. The Provider is wrapped around a component and allows its descendants to access the context data, i.e. it’s the context “giver”.
  5. A Consumer component “consumes” or accesses this data. Context can be used with the useContext hook in functional components to get the current context value. In class components, the Context.Consumer component is used.

Best practices for using the context API

It’s easy to get shiny object syndrome (literally) with React Context, but resist! Here are some guidelines to think about when considering if context is the best tool for the job.

Use sparingly

Overusing the Context API can make it more difficult to understand how data is being passed through your application. In general, you should use props for passing data down the tree in React.

Only use the Context API if props are becoming too deeply nested or if it needs to be shared across multiple unrelated components.

Avoid using for complex application state

The Context API is intended for sharing simple data or functions across multiple components. For handling complex application state, a state management solution like Redux is a better fit. Redux is useful for managing data that needs to be updated in response to complex interactions or network requests. It also allows you to access data across multiple components or routes.

Consider performance

Use the latest version of the Context API, if possible. The Context API has undergone several updates since its introduction. The latest version (available in React 16.3 and higher) provides improved performance and flexibility.

While the Context API can prevent re-renders due to prop drilling, we’re not totally free here. A component using the useContext hook will still re-render when the context value changes. If re-rendering the component is expensive, make sure to use the useMemo or React.memo higher-order component to memoize the context value.

The verdict

The React Context API can be a great solution when data gets too nested or is relevant to many different components. By now you hopefully understand when to use the context API vs. prop drilling and the core concepts of the Provider (context giver) and Consumer (context getter). When used sparingly and considering performance, it’s a great tool in your React toolkit.