How and when to use React Context

React context can be a very powerful tool, but when and why should you use it?

Are you sick of redux boilerplate, tired of wating for recoil to finally release a stable version, afraid of misspronouncing the word "zustand"? Well, you're in luck! React context is here to save the day! Or is it? Let's find out!

What is React Context

To put it simply React Context simply another way that you can tackle your app state or global state in your react or next.js application. React context allows you to create a state provider or "context" that wraps around your components and lets them access the it's state. You can have one big global context and simply wrap your entire application in it, or you can have multiple isolated contexts that are only used in specific parts of your application that needs to share a common state.

All this without the need for a third party library, and without the need to use redux or any other state management library. Sounds great, right? Well, it is, but there are some downsides to using context. Which we'll get into later.

When to use React Context

This is the big question when it comes to react context, it falls into this odd category where you don't really need to use it for small and simple applications and if you're building a very complex big application with a lot of moving parts you're probably better off using something like zustand or even redux if you're a masochist.

So when should you use react context then? A good rule of thumb is to use it when you want to share a specific state between multiple components, but you're fairly certaint that state is not expected to change very often. A good example of this would be: a theme switcher. You want to be able to switch between light and dark mode, but you don't expect the user to switch between the two modes very often. So you can create a context that holds the current theme and wrap your entire application in it. Then you can use the context to change the theme and all your components will be able to access the current theme.

Another good example would be a language switcher. You want to be able to switch between different languages, but you don't expect the user to switch between the languages very often. So you can create a context that holds the current language and wrap your entire application in it. Then you can use the context to change the language and all your components will be able to access the current language.

There are multiple reasons why you should not use react context for frequent state changes. The first reason is that react context makes it very easy for the developer to create useless re-renders, this can easily be avoided by utilizing the useMemo or useCallback hook. However we should be pretty picky on when to use these two hooks as their overuse can slow down your application even further. Then there's the problem of react context not really handling growing app complexity all that well. For example if we have something like zustand we can split our state into multiple stores or "slices". Each slice can contain any number of data and/or methods. We can then call these slices from anywhere within our application and access their methods or members. React context also ... kida sorta maybe does this, you can immitate that functionality quite well with react context but it's never as scalable or extensible as something like zustand.

So when should I even use react context?

Apart from the already aboved mentioned examples here's a good "flowchart" I like to follow mentally when deciding whether or not to use react context:

Q: Do I have an issue that would require prop drilling deeper than 3 levels deep?

A: Yes.

Q: Would it not make sense to put it into redux or zustand?

A: Yes.

Use react context.

How to use React Context

Okay so now that we've basically trampled all over react context, you're probably thinking

Why Dave you must absolutely hate react context and probably think it's trash.

No, not at all. I love react context, I think it's a godsend. In fact I think it's an insanely useful tool that can solve a lot of problems and make your life as a developer a lot easier. But just like every tool it's important to understand what it's intended for and what it's not intended for. You wouldn't wanna hammer in a nail with a screwdriver after all and you probably would not assume the screwdriver is trash just because it's not a hammer.

Creating a context

Creating a context is super simply all you have to do is call the createContext function and pass it a default value, or initial state. If you're using typescript and bonus points if you are this will let react context know what kind of data it can expect to store.

import { createContext } from "react";

// Create the context
const LanguageContext = React.createContext({
  language: "en",
  setLanguage: () => {}
});

Above we've created a context that holds the current language and a method to change the language. We've also set the default language to english or "en" for short.

Context provider

Now that we've created our context we need to create a provider for it. The provider is the component that wraps around all the other components that need to access the context.

import { createContext } from "react";

// Create the context
const LanguageContext = React.createContext({
  language: "en",
  setLanguage: () => {}
});

interface IProps {}

// Create the provider
const LanguageProvider = (props: React.PropsWithChildren<IProps>) => {
  const [language, setLanguage] = React.useState("en");

  return (
    <LanguageContext.Provider value={{ language, setLanguage }}>
      {children}
    </LanguageContext.Provider>
  );
};

Here we've added a context provider that can later wrap around all the components that require access to the context. This is done via the children prop which we then access by destructuring props.

Using and updating the context

Now that we've created our context and our provider we can finally start using it. To use the context we simply need to import it and then call the useContext hook. The useContext hook takes the context as it's only argument and returns the context's value.

import { createContext } from "react";

// Create the context
const LanguageContext = React.createContext({
  language: "en",
  setLanguage: () => {}
});

interface IProps {}

// Create the provider
const LanguageProvider = (props: React.PropsWithChildren<IProps>) => {
  const [language, setLanguage] = React.useState("en");

  return (
    <LanguageContext.Provider value={{ language, setLanguage }}>
      {children}
    </LanguageContext.Provider>
  );
};

// Use the context
const LanguageSwitcher = () => {
  const { language, setLanguage } = React.useContext(LanguageContext);

  return (
    <div>
      <button onClick={() => setLanguage("en")}>English</button>
      <button onClick={() => setLanguage("sv")}>Swedish</button>
    </div>
  );
};

Now whenever we want to change the language we can simply call the setLanguage method and all the components that are wrapped in the provider will be able to access the new language.

Conclusion

React context is a very powerful tool that can be used to solve a lot of problems. However it's important to understand what it's intended for and what it's not intended for. If you're building a small and simple application you probably don't need to use react context at all. If you're building a very complex application with a lot of moving parts you're probably better off using something like zustand or even redux(masochist). But if your use case is wanting to store some piece of state somewhere and not expecting it too change very often and you wish to keep it independant from any third party libraries AND you also don't really feel like prop drilling too much then react context is probably the best tool for the job.