Why React’s new Hooks API is a game changer
Finally we get rid of second rate coding patterns in React
React’s new Hooks API allows us to finally share behaviour without any of the intrinsic problems that have plagued the community previously
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:
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.
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.
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:
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:
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.
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.
Conclusion
The ideas presented in 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.
For more information you can check out their write up on the React website and 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