How we handle React Context

David Barral
Trabe
Published in
3 min readJun 10, 2019
Photo by Patrick Tomasso on Unsplash

In React, the UI is composed by components: independent, reusable pieces that communicate with other components via props.

Conceptually, components are like JavaScript functions. They accept arbitrary inputs (called “props”) and return React elements describing what should appear on the screen.

It’s an elegant design but makes things cumbersome when dealing with cross application concepts like i18n or authentication, where you need to manually pass props down through deep component trees. To fix this, the React Team created the Context API.

React Context started as an experimental API tied to prop-types and class components, and later evolved into its definitive Provider/Consumer approach on the 16.3 release . It’s fair to say that it’s another piece of elegant design and fits nicely into React’s component based architecture.

Then came React 16.8 and Hooks.

Hooks let you use state and other React features without writing a class. You can also build your own Hooks to share reusable stateful logic between components.

Hooks support React Context via the useContext Hook.

At Trabe, we have been using React Context since our first projects. We also have embraced Hooks. We think that’s the best way to write our components. Let’s see how we use React Context nowadays combined with Hooks.

A simple example: an auth provider

We need to share across several components some auth info like the current logged user. Other components may need to login or logout an user. We use the Context to pass the user info and some operations down the component hierarchy. Components will grab both user and operations using a custom Hook.

With the provider and the Hook in place we can use it in our app with ease.

Notice what we did:

  • We passed an API, not just a value. Child components may need to affect the state of our provider component.
  • The component handles its own state. The API methods call setState as needed.
  • Child components use a custom Hook to grab the API. The use of React Context does not leak to the children. Our custom Hook just delegates touseContext, but it could have added some sort of wrapper or filtered the context value if needed.
  • We rely on useMemo to memoize our auth API. It will only change when the user stored in the state changes. If we don’t memoize the API, a setState in the provider will create a newContext value, forcing all consumers to re-render even if that value is the same (the user has not changed). You can probably skip this memoization in most cases, but it won’t hurt. There’s a lot of room to talk about how and when to use useMemo and useCallback, but that’s out of the scope of this post.
  • We set the context default value to a “null” API. Remember that this is not the initial value stored in the context. It’s a default value that consumers will see if they aren’t nested under a provider.
  • We do not rely on the default context value for tests. We pass all provider props to the Context.Provider, effectively allowing a test to overwrite the Context value passing a value prop.

To support the static contextType API in classes we could export the Context, but that’s leaky and couples our code to the React Context. For class components we pass everything a component needs as plain props, and wrap the class component with a custom consumer (with a render prop) or a Higher Order Component.

These approach is how we also handle legacy components. When using the old experimental Context API we tried to hide as much as possible the use of context. We relied on these custom consumers or HOCs to avoid coupling. Transition to the new React Context API was painless: just switching to the new consumer or HOC version.

Summing up

The React Context API is great. It solves with elegance the problem of passing stuff deep down your component hierarchy. However, it’s a good practice to avoid coupling your components with the Context API. It’s easy to do so by making your components receive what they need via props or using custom Hooks.

--

--

David Barral
Trabe
Editor for

Co-founder @Trabe. Developer drowning in a sea of pointless code.