Technology

Deep dive into new React context API

React 16.3 release introduced a long-awaited rework of the context API!

Until now, context API was considered experimental. If you’ve used it before you’ve probably seen the warning message in the console forecasting its rebirth.

This is why I adore the React core team, they take backward compatibility seriously and are careful not to introduce an API that might be rendered obsolete in near future.

It’s often a good practice to defer decision-making processes as late as you can; early and rushed decisions will only cause unforeseeable impact leading to an inevitable rework of existing parts.

Image: “Blue sand falls in an hourglass on a rocky beach” by Aron Visuals on Unsplash

React team being aware of this, implemented the first context API since it’s a critical feature, but declared it experimental. This way, we were able to use it while the React team deferred the official implementation producing a much more mature and stable API in the future — And the future is now! 🙌 🎆

So, what’s the Context API?

Essentially it’s just a dependency injection strategy. Context makes it possible to provide data directly to any component without using props.

  • Provider
  • Consumer
  • React.createContext

These are the only three things you will need to understand to use context.

Provider

A React component that allows Consumers to subscribe to context changes.

Accepts a value prop to be passed to Consumers that are descendants of this Provider. One Provider can be connected to many Consumers. Providers can be nested to override values deeper within the tree.

Consumer

A React component that subscribes to context changes.

Requires a function as a child. The function receives the current context value and returns a React node. The value argument passed to the function will be equal to the value prop of the closest Provider for this context above in the tree. If there is no Provider for this context above, the value argument will be equal to the defaultValue that was passed to createContext().

Let’s get our feet wet. 💦

React.createContext("Scuba Manager") creates a new context with default value of “Scuba Manager”. createContext returns an object containing a Provider and Consumer components which we can now use to, you’ve guessed it — provide and consume our newly created context anywhere in the tree!

In the example above, the value prop was passed to the Provider hence overriding the default value specified upon context creation.
If no value prop was passed to the Provider, Consumer would fallback to a default value which is “Scuba Manager”.
⚠ Note that passing a value prop with a value of undefined doesn’t cause the context to fallback to the default value, instead it is accepted as a value.

No more prop drilling 👷

When a deeply nested component needs data that is defined far up in the component hierarchy we must pass that data through props of every intermediate component until we get to the targeted component.

Image: Without context (using props)

Component tree is bloated with the currency prop.

App component defines the currency, e.g state = {currency: "USD"}Payment component needs currency to render the amount of money.
Since Payment is a rendered deep down in the tree, all of its parent components now must expect currency prop and pass it down to its children until it propagates to the Payment.

Prop drilling doesn’t only bloat our codebase but also reduces reusability of “drilled” components. PaymentScreen and PaymentList should not know about the currency, their job is to render the view rather than provide dependencies to the children.

Don’t use context just to avoid passing props a few levels down. Stick to cases where the same data needs to be accessed in many components at multiple levels. — React docs

As you can imagine, context is easy to abuse. We should always stick with props until complexity of the tree increases so much that your brain lags when trying to comprehend the entire structure or more likely, just to avoid unnecessary bloat.

We don’t need Redux anymore!

If you just removed Redux from your app you might as well
$ yarn add redux react-redux

If you’re only using Redux to avoid passing down props, context could replace Redux — but then you probably didn’t need Redux in the first place.
– Mark Erikson (Redux maintainer)

Context is not to replace Redux. React-redux uses context internally to provide data to connected components on store updates.
Redux is a complete state management solution (store-reducers-actions) while context is just a dependency injection.

We’re waiting for a React-redux release that will implement the new context API. There’s already a functional branch which implements it.
Follow the steaming hot discussions regarding the new context API regarding async rendering in this pull request and roadmap discussion.

Emerging patterns & pitfalls

Thanks to the simplicity and practicality of the new context API, there are new evolving ideas and patterns emerging in the ecosystem.

Unstated is a new minimalist state management alternative to Redux.
No actions, no reducers, just setState — Sounds perfect for small apps where Redux brings more overhead than management.

Composing multiple Consumers
Since the new API adopted render props pattern it also inherited its imperfection when it comes to nesting multiple render props. Code readability decreases significantly with each additional render prop.

With the use of React-composer, we can avoid this code drift by composing consumers.

Context shadowing

I haven’t seen a use case anywhere yet, but according to the docs nested Providers override parent Provider’s context which is analogous to variable shadowing.
I feel like there might be some interesting patterns emerging from this possibility but I’m really stretching my imagination right now. If you’ve seen any patterns which rely on this feature please let me know. ☎

Thank you for reading! Do you disagree with my take on context? Did I miss something important? — Let me know, I’d love to hear from you.

Not enough, give me more!