Demystifying React Context
What is context, and what does it do?
Context allows you to set up a Provider component which allows Consumer components to subscribe to context changes. The consumer components do not need to be immediate children of the provider in order for the context to be passed down to them. The consumers can also be given access to methods that allow them to update the context (which in turn is broadcast to all other consumer components). This is useful when you have information that many parts of your app need to know. For information that only a few components need, other forms of state management, such as React Hooks or Class components, are likely a better fit.
Let’s walk through a simplified example where several components in our app are going to use a theme (“light” or “dark”) that a user can toggle. In this example we will:
- Create a Context Object using createContext
- Set up state and methods to be passed to our ThemeContext.Provider
- Write a custom useTheme hook that will give our components access to the state and methods we passed to our ThemeContext.Provider
- Subscribe our components to ThemeContext using our custom hook
Let’s start by setting up our theme Context in ThemeProvider.js. The first thing we need to do is call React’s createContext with a defaultValue of null to create a Context object. Don’t worry too much about that null value for now; it will help us with error handling later. The main thing we want from this line is access to the Provider component that comes with every Context object.
I want to keep all of the state and methods related to my theme in this file and out of my main App.js, so I’m going to create a component called ThemeWrapper. In this component, I’ll give “theme” the default value “light” with the useState hook.
I’ll also create a function called toggleTheme that changes the theme from light to dark and vice versa.
Here I think it’s important to point out that my ThemeWrapper component itself is not the Provider, but I’m going to set it up to return the ThemeContext.Provider around props.children. Children is a special prop in React, and is effectively a placeholder for anything and everything between the opening and closing tags when the component is invoked. In our case, we want anything wrapped by our ThemeWrapper component to be rendered as a descendant of the ThemeContext.Provider so that those components can subscribe as a consumer if necessary.
What would they be subscribing to? Provider components (i.e. ThemeContext.Provider) accept a value prop that becomes available to consumer components. In our case, we want them to have access to the theme and the toggleTheme method.
One last step before we start using our context — let’s create a custom hook to get access to our ThemeContext.
Essentially, our useTheme hook is going to check whether we’re trying to use the theme context within our ThemeContext.Provider (where it will have access to the values we set up above). If not (this is where the default null value from before comes into play), we’ll get a helpful error message. Our useTheme hook gets its real power from the default React hook useContext. Here, useContext is taking in our ThemeContext, which will give us access to the value and method we passed to our value prop in the ThemeContext.Provider. Whenever that value prop updates (i.e. when we change the theme using our toggleTheme method), this hook will trigger a re-render of the component using useContext — or in our case — using it by extension via useTheme.
Here is what the full ThemeProvider component looks like when we’re done:
Let’s use our theme! This is the easy part 😁
Let’s say we have three components in our app:
- A Header component that will change based on our theme
- An UnthemedComponent that won’t subscribe to our theme
- A ThemeButton component that will toggle our theme and update accordingly
In order to give them access to our theme, we’ll wrap them in our ThemeWrapper component, making them the props.children of our ThemeContext.Provider.
From there, using our theme is as easy as importing our useTheme hook and calling it to get access to the current theme and toggleTheme method. Our ThemeButton will look something like this:
Our Header could look something like this:
What about our UnthemedComponent? No need to do anything special — it won’t subscribe to changes in our theme unless we tell it to!
If we want more components to be subscribed to our theme, all we need to do is use our useTheme hook the same way!
Happy coding!