Why React’s new Hooks API is a game changer

By Rudi Yardley

React’s new Hooks API allows us to finally share behaviour without any of the intrinsic problems that have plagued the community previously

I have been developing with React since it’s 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 glimpse of their new API for sharing behaviour in React components that looks like it will solve 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…

When React was first released, classes were not available in ES5 so React shipped with it’s 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.

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 (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 are a relatively new trend and offer an 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 it’s children. This allows the component to provide a closure for it’s 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

Since it’s 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

Two days ago 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 by default 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.

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

example courtesy https://reactjs.org

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.

After thinking about this and reading through the docs for a bit I think I am beginning to understand why the React team has gone down this path.

The react team says they will support us with a suite of linting plugins. Not everyone uses linters however yet I would assume that they will at least attempt to include some kind of friendly runtime error as well eventually although detecting if a bit of code is in a conditional might be impossible at runtime. So this is a shame. Maybe over time the React team will work out clever ways to prevent trip ups for codebases that are not linted as well as on-board new developers to the API. After thinking about this and reading through the docs for a bit I think I am beginning to understand why the React team has gone down this path. Alternative syntax implementations will involve more boilerplate and to be fair this solution is actually rather ingenious.

The hooks API is a boon for React developers. Finally we have a relatively baggage free API for developing React components. We no longer need to worry about refactoring from functional components to classes and can share behaviour without confusing indirection.

I look forward to seeing libraries such as ApolloClient and Redux develop their own hooks API components for implementing behaviour.

You can try React hooks API in the React 16 alpha release:

$ yarn add react@v16.7.0-alpha

Also for more information you can check out their write up on the React website have a look at the comments on their RFC or watch the announcement demo below:

This article is a living document please reach out to me if you want to contribute or see anything inaccurate here.

You can follow Rudi Yardley on Twitter as @rudiyardley or on Github as @ryardley