As applications grow in scope and complexity, code cleanliness becomes an increasingly important aspect to consider and manage. Let’s look at how we can utilize custom hooks along with some helper components to manage context within React.
Context API & React’s useContext hook — A quick overview
To keep this article brief, I’ll assume a basic understanding of how React’s Context API works. But in a nutshell, context provides a way to share global data across your application at multiple levels without having to resort to prop drilling.
Not only can context be implemented when using functional components thanks to the useContext hook, but context becomes much easier to manage by removing the need to explicitly declare context consumers and saying goodbye to those ugly callbacks when consuming multiple contexts!
Looking at useContext in action
Let’s look at a basic implementation of context in a functional component. For this example, I’ll be using a slightly tweaked version of the useContext documentation example.
Here we have a basic app which renders a toolbar (just a div wrapper to keep it simple), which contains a ThemedButton component. ThemedButton renders a simple button which is styled by the current theme state and whose theme can be toggled by clicking the button.
Within the top parent App component we provide the component with a theme state, using React’s useState hook, which captures the current theme style in an object.
Additionally, we create a toggleTheme function which changes the theme state from light to dark, and vice versa.
By passing both the state and the toggle function to ThemeContext.Provider, any components that consume this context can access this data and will re-render when it updates.
This implementation works for simple examples, but if you were to want to add more contexts to this application, the App component would quickly become cumbersome to look at and be more difficult to work with.
Cleaning up the implementation
Now we’ll take the same application and clean up some of the implementation. Rather than keeping the logic relevant to the application theme inside the App component, we can extract this into its own file and export a custom hook and a component wrapper which components can then consume.
First, let’s pull all the logic surrounding the theme context out of the App component.
Here we export a very simple custom hook which just calls the useContext hook with the ThemeContext as a parameter. By doing this we remove the need for all consumers of this context to always need to import both useContext and ThemeContext, reducing the complexity of these components.
Within themeContext.js we also export a helpful component which accepts children as one of its props. Within this component, we also contain the theme state and the toggle function we implemented earlier.
By wrapping the children prop in our ThemeContext provider, we can easily wrap any component to easily consume this context! Let’s look at this in action.
Looks like a much cleaner implementation. You can see that by importing the ThemeContextProvider wrapper that we created, our App component has been immensely simplified. By wrapping the Toolbar component with the ThemeContextProvider, we allow any child component (and all of their children) access to consume this context.
You can see that the ThemedButton now calls the custom hook we created earlier rather than React’s useContext hook with ThemeContext. In fact, our components no longer need to directly import any context or useContext hook directly!
Following this same structure, adding and managing additional contexts is clean and easy!