Hooks, yeah! What are they good for?

Will Madden
The Startup
Published in
6 min readNov 14, 2019

Hooks have caused a lot of commotion since their release in React 16.8. There’s been some discussion about their usefulness but overall they’ve been enthusiastically received by the JavaScript community.

Many prominent figures are asserting that “hooks offer obvious improvements over class components” and allow you to “remove tons of code”, without providing much clarity about how exactly hooks do that. So let’s examine what hooks let us do that we couldn’t do before, and at what cost.

Because all good things come at a price.

Thinking in React

The basic building block in React is the component. A component corresponds roughly to a piece of the visible UI, e.g. a<Button> in your code maps to a button on the screen. How exactly that JSX becomes onscreen UI is determined by data passed in as props.

A block of code that takes inputs and returns outputs can be easily represented in JavaScript by a function. So React components are functions.

The mental model of components mapping inputs to UI is the one-way data flow that makes React famous — and from which it gets its name. It’s easy to grasp and reason about because the framework’s mental model is mirrored in native JavaScript concepts (component ≈ function, in this case).

Of course, eventually components need to keep track of internal state. So our component functions which take props and return UI now have associated state, and functions associated with state are — in object-oriented languages — typically represented by classes.

So React provides the Component class, which gives us the setState() method. The Component class gives allows us to associate rendering logic with state in a native language construct, but the framework’s mental model starts to break down and unexpected, exceptional behavior creeps into the setState() method. The React docs have a whole section titled “Using State Correctly” describing this unexpected behavior.

The other significant element of the React model is the component lifecycle: a component will be mounted, updated and unmounted. When it passes through those states its lifecycle methods will be called, but representing state transitions with methods causes some common problems because of the methods’ close relationship to one another.

A common example is subscribing to updates in componentDidMount() which requires unsubscribing from them in componentWillUnmount(). This results in closely related code residing in two separate methods. Even worse, any other code which needs to execute during lifecycle events has to be called from the lifecycle methods, so unrelated code ends up living together in the same method.

Related code which is distributed over multiple methods is difficult to extract and reuse. To solve this problem, techniques such as higher order components and render props emerged. Both techniques have the drawbacks that:

  1. They introduce components into the React component tree which don’t correspond to a visible piece of the UI (wrapper components)
  2. They make it difficult to identify where state or behavior in the final component originates

We can no longer think in terms of components that map input props to visible UI. Our mental model no longer matches React’s reality ofsetState(), mounting, unmounting, HOCs, render props, wrapper components and hidden state.

Rethinking React

Hooks are the React core team’s attempt to address these issues. To understand how they do that, let’s look at the shortcomings of class components and compare them to the hook equivalent.

Hooks only work with function components, and the basic premise is the same: a component is represented by a function which, given input props, returns UI. A hook gives a component function powers that native functions don’t have.

The first one is the ability to maintain state between calls. In Component classes state is maintained on the component instance and manipulated through the setState() method. Maintaining state on a class instance is a native object-oriented concept, but it doesn’t work that way in React. You can’t modify state directly, updates are asynchronous and merged and this React-specific behavior is a frequent source of errors.

Hooks define a mental model for state management with much more straightforward behavior:

  1. Declare that you’ll use a piece of state
  2. Give it an initial value
  3. Change it when you’re ready

The promise React makes is that your component will be updated when its state changes, and the details are hidden from view. As a result, none of the exceptional behavior inherent in setState() affects a developer using state with hooks.

The second special power hooks give functions is the ability to cause controllable side effects. With class components, if your component caused a side effect it was important to use componentDidMount() to make sure the side effect only happened once (not every render), and equally important to clean it up afterward in componentDidUnmount().

Hooks’ mental model here is also straightforward:

  1. Declare that you’ll cause a side effect
  2. Do it
  3. Specify how it should be undone in the future

And React promises to clean it up when the time is right.

Since developers are no longer exposed to the component lifecycle, it’s no longer necessary to represent them in code, and the problems that arise from lifecycle methods don’t occur. Consequently the HOC and render prop techniques are no longer necessary.

What’s the tradeoff

The tradeoff of introducing hooks to React depends on how you look at it.

React gained popularity quickly because it was simple, and because the mental model of the framework was expressed in native language concepts. So anyone who understood JavaScript could understand React and reason about how it would behave.

Hooks are a change to React’s mental model and they’re not a concept that occurs in the native language (although as Eric Elliott mentioned, maybe one day it will be possible to implement them using algebraic effects). Now anyone who learns React with hooks has to learn a new concept.

For applications that don’t use class components, and for teams who understand how to use hooks, there will be only one way to define a component and a consistent mechanism to define reusable logic which is idiomatic to the framework, if not the language. That’s a huge improvement!

But most production applications are already strewn with class components and existing examples of code reuse via HOCs, render props and other techniques. So introducing a third method of defining components will fragment their codebases.

Lesser frameworks have been killed by such a confusion of competing methods and concepts. But the React core team has a strong voice with well-reasoned arguments, and their direction might give enough clarity to the community to offset the confusion caused by the upcoming period of migration from the pre- to post-hooks world.

Eventually we’ll see what effect hooks have on React’s learning curve and popularity. In the meantime, they solve a lot of real-world problems and you’ll ultimately have to decide for yourself whether your particular team and project would benefit from adopting them.

What did you think of this article? I’d love to hear your thoughts.

I’m the frontend lead at Remerge, where we put a lot of thought into how we write our code. If you’re like us — passionate about writing the best damn applications you can — we’re looking for new teammates.

Our office is in Berlin, Germany on the top floor of a beautiful old building in the heart of the city. Read more about us here.

--

--

Will Madden
The Startup

Passionate about making great things with great people.