Sharing behaviour between components

I have been developing with React since its early days and during that time there have been many attempts by both influencers, as well as the core team to improve the API and patterns developers are using to creating software. One of the biggest challenges we have had was how to share behaviour neatly between components to enable reuse or even just separation of concerns. Every single solution proposed up until this point had some problems associated with it.

Luckily React has just released a their new API for sharing behaviour in React components that solves many of the problems we have had in the past.

This is what it looks like:

An example of the new hooks API courtesy https://reactjs.org

First lets have a little look at how we got here…

Mixins and magic methods

When React was first released, classes were not available in ES5 so React shipped with its own class creation method, which included the ability to merge in a bunch of methods from an object into the component you create.

An example of the old mixin API

This unfortunately led to similar problems you find with classical inheritance; mainly indirection from magic undocumented methods appearing out of nowhere and being used on components. The developer has no idea what functionality is available to them and more importantly what is not.

This smell was so bad the React team decided to remove Mixins completely when they introduced a new ES6 class based API.

Things got better but there is still a problem

Both the more recent attempts at sharing functionality between Components, namely Higher Order Components and Render Props, have also fallen short on an API level for several reasons.

Higher Order Components still cause indirection

Higher order components (or HOCs) are an attempt at applying the Functional Programming concept of higher order functions to React components. The idea is that you alter your component by wrapping it in an outer component that provides behaviour, composing the original component and passing the behaviour’s results as new props to the original component. This is done in a similar fashion to the way higher order functions pass data via closures.

Higher Order component example courtesy https://reactjs.org

What’s great about higher order components is that you can see the data coming into the component as a prop. It is no longer magical like with Mixins.

However there are issues. The main problems with higher order components include:

They are complex to setup.

You can not distinguish between data that was coming from the HOC and the data that was passed to the component.

The HOC is external to the component yet the component remains dependent on the HOC. Remove the HOC and the component would not always work if it depends on the HOCs data.

You can end up with huge render trees as behaviour components contain render components.

Render props and the pyramid of doom

Render props are a relatively new trend and offer an answer to some of the dependency and indirection problems that HOCs can cause. They are created by enabling a component to accepts a function prop that it will use to render its children. This allows the component to provide a closure for its children as well as some behaviour and new data.

However they can be abused. See this (event though contrived, unfortunately rather typical) Apollo React example:

An example of render props pyramid of doom!

If you have ever worked with me you probably know my hesitation around using render props. I think it can be a useful pattern in certain contexts however it has a few major issues:

It declares false hierarchies ie. pyramid of doom.

Encourages passing inline functions to child components which if not checked can lead to performance problems.

Create confusing closure structures which should actually be inline.

Leads to very verbose component JSX

In fact class lifecycle methods also suck

Since its inception React has included various lifecycle methods for developers to hang code on based on particular execution times within the lifecycle of the component being rendered in React. Being able to support this asynchronous behaviour is why React components were modelled as classes to begin with. This model is simple and it provides an intuitive way to attach behavioural code to a Component.

There are problems however with this approach. What tends to happen in practice is that code relating to a particular functionality ends up being scattered all over the various lifecycle methods of the class and usually right next to code from unrelated behaviour. This commonly happens whether or not you are using HOCs or render prop components. Also by using classes you inevitably need to use the JavaScript this object which means you need to understand and take care of binding your handlers as you pass them around to child components.

React 14 introduced stateless functional components to resolve this but they did not provide ways to access lifeCycle methods which relegated them to only be used on components that would not grow into requiring complex behaviour

Enter the React “hooks” API

At ReactConf 2018, the React team announced their new hooks API. React’s new API attempts to solve these problems by making HOCs and render props obsolete. The new API allows for true state driven behaviour sharing while also:

Providing a way to get access to state managed props and be able to easily follow exactly where that state has come from.

Returning memoized functions avoiding performance penalty from downstream PureComponents

Not creating a pyramid of doom

Not touching props. What you pass into the component in JSX is what you get in props.

Not creating any magic behaviour methods.

Leading to simpler JSX that is more concerned with component rendering and less with behaviour.

Removing the performance overhead of wrapping components in layers.

Allowing for custom behaviours to be bundled off into their own functions that can be exported by libraries.

Example of the Hooks API

Here is an example of how to create a custom hook:

example courtesy https://reactjs.org

There are a few minor drawback to the new API

The main drawback to using the hooks API is that all the “hooks” methods must be run in the same order every time the component is rendered.

This means that you cannot call hooks functions within if blocks or loops within your functional component.

In fact when I first heard about this I was a little concerned. I don’t like the idea of hidden rules I have to follow. I think it means that new folks coming to the API will struggle to understand why their code is not working.