We Have to Talk About Your love for React Hooks

Pimm "de Chinchilla" Hogeling
The Startup
Published in
4 min readJul 28, 2020

--

Since their introduction in early 2019, React developers have embraced hooks. And there is a lot to love. But some of you seem to be in love with hooks.

Hooks can help design components which are simple and easy to read. Yet that does not make adding a hook the right solution for any problem. In some cases, an added hook can actually unnecessarily complicate the workings of a component. This is especially true if multiple hooks interact with each other.

These are three hook-based patterns I frequently see in the real world. I will offer you alternatives that are less hook-y yet easier-to-read.

A cartoon person holding a fishing rod, gazing at a butterfly, asking “is this a fish?”

The Derived State

Say we have a component in which the user chooses a begin and end time, and then shows the duration between those two moments as feedback.

This (sort-of¹) works. When either begin or end changes, the effect fires and duration is calculated.

The presence of the duration state hints that something is not right. It is derived from begin and end, and as such it is not state itself.

That’s two hooks instead of four, and the code is more obvious.

But”, I hear you think “wouldn’t this trigger that calculation more often than necessary?” Yes… maybe. Performing a calculation directly from the component function is not a good idea if:

  1. the calculation is expensive, and
  2. the component has good reason to rerender other than the parameters of this calculation changing.

This is the exact raison d’être of useMemo.

If calculating the duration in the render phase feels odd, consider that while begin and end will be submitted somewhere; the duration only exists to serve the UI.

The Bubbling Effect

Say we have an egg that cracks as it is clicked.

Now we want the egg to hatch after ten clicks. By making count its dependency, we create an effect which fires after each increment.

Does the trick². Moving the onHatch call outside of the effect, placing it directly in the component function, would be a poor choice here. This seems like proper use of an effect…

The code becomes simpler when instead of thinking “call onHatch when count is 10”, you think “call onHatch at the 10th click”.

No more unnecessary hook, and the relationship between the click and the onHatch call is more evident.

If there were more than one way to increment count, it would still not warrant a useEffect. The callback supplied as a prop here could be moved to a variable and then used in several places.

A cartoon person holding a big egg.

The Component That Knew Too Much

The third and final pattern concerns components which use a web of hooks to implement complex logic not directly relating to the UI.

I have seen a login form component that handles the entire authentication process. Tokens being exchanged, verified, and stored, all through a complex network of useStates and useEffects.

I have seen a grid of images that delays the loading of those images until they are scrolled into the view. It kept track of the loading state of all images through a similar spaghetti of hooks.

The name “hooks” I think is very appropriate, because they allow your components to “hook into” things. This includes things outside of the realm of React.

This touches on the age-old practice of splitting by responsibility. A component can be responsible for triggering a complex job as well as for displaying the result of that job, but it should probably not be responsible for the job itself. Remember: you can still write regular JavaScript functions and classes! And you can hook into them from within your components.

One short custom hook and a very concise component. And because doComplexJob is untangled from the UI, it is very straightforward to unit test.

Do not let those butterflies in your belly cloud your judgement. Incorporate hooks, but only where they help you.

¹ The version of <PeriodPicker /> with useEffect ‒ due to the very nature of that hook ‒ renders twice: once with the old duration and then once with the new one immediately after.

² There are subtle differences between the version of <CrackingEgg /> with useEffect and the version without it. The former rerenders and then calls onHatch; the latter reverses that order. Additionally, the latter calls onHatch at most once, while the former could call it multiple times if the onHatch prop itself changes.

Feel free to copy this post: the text is licensed under CC BY 4.0, the illustrations are not.

--

--