Transforming React Context into Properties

It’s no secret that the React Context API will probably change in the near future. If you use context like myself, this might be worrisome.

Context is an advanced and experimental feature. The API is likely to change in future releases. — React Documentation

However, sometimes context is a necessary evil. Why is context considered bad? When you use context your components become impure. If you need to use context, the general recommendation is to deal with it in a higher-order component.

So, that’s our goal. Use a single higher-order component to convert context into regular ‘ol props — an API that we can rely on.

Why use Context at all?

In my case I am using React Router, particularly the route children feature. My parent route is responsible for loading data from the server, but the child routes need that data as well.

Does this absolutely require context? Nope. Previously, I was communicating this state to child components using Redux. The problem I had with that approach was that this particular state was completely tied to the component lifecycle — so it felt more ‘natural’ to put the state back into the parent route component. Because we cannot pass props from parent routes to child routes, context is necessary.

What We’re Dealing With

If you use the context API you probably understand why the API is likely to change: it requires a lot of ceremony and has a lot of gotchas. The setup that we’re playing is semantically similar to this:

It’s clunky. It’s unstable. Let’s fix that.

Implementing our Higher Order Component

Going back to our goal, we want to transform context passed from the Parent into props on Child. To do this, we’re going to use a higher-order component. Before we see the code, let’s review what we want to do. We want to create a component that wraps another component, but passes it’s context onto the wrapped component as props. It’s actually not very complicated!

Let’s break this HoC down with a bottom-up approach.

  • The last thing we do in the withContext function is return a component called ContextComponent.
  • Ignoring the functions around it for a moment, ContextComponent just declares the context that it is looking for in the same way a regular, non-higher-order component would.
  • When ContextComponent is rendered, it spreads the context that it has received onto it’s child. This passes the context to the child as props.
  • Next, instead of hard-coding the children as a non-higher-order component would do, we wrap ContextComponent in a function that accepts any component as input — this becomes the child to ContextComponent. If you’re unfamiliar with HoCs, this is the distinguishing feature of one.
  • Finally, we wrap our previous function in yet another function. This is not required, but the intent will become clear in a moment.

Great, now we have a mechanism for transforming context into props! Let’s apply it to our original example:

Using our withContext HoC, we were able to remove all notion of context from the Child component and read that data from the props — hooray!

But, withSomeContext1(withSomeContext2(Child)) looks pretty hairy, especially if we need many more keys from context. We can borrow an idea called ‘threading’ from Clojure to make this look a little neater:

This new function thread takes an argument and successively passes it to each function in funcs. Here’s an example usage outside of React for clarity:

Utilizing thread in our previous example, we end up with the final product:

“Wrapping” Up

After all of that, what was the point? What did we gain? We gained two benefits:

  1. Our child component is pure again!
  2. When the React context API changes, we have a single place, withContext, to upgrade. Remember Sebastian’s tweet?