How to Reuse Logic with React Hooks

June 25, 201924 min read

Last reviewed June 25, 2019

This article was originally posted in the ButterCMS blog.

React has always been great for reusing and composing components. That means you can write a piece of UI and simply reuse it later on. Moreover, your component can have some embedded logic that you can reuse too. Write once, and use everywhere. A big win!

But what if you want to reuse the logic only? It means that you don’t care about the UI, but you care about the behavior of the component. Can you still write it once and use it everywhere?

And the answer is… yes! React developers have designed all kinds of patterns for reusing logic, the most prominent being Higher-Order Components (HOC) and Render Props. They work and have been in use for quite a while. But still, those two approaches don’t feel quite right and have some shortcomings (which we will see soon). What could be a possible alternative?

As of React 16.8, Hooks were introduced. Hooks are a way to reuse logic across applications. You write a specific behavior (logic) and “hook” it into any component. This component now can use this logic normally.

Why do we need React Hooks?

Consider that you want to add a clock into your application. Clearly, you think, this is something very common that someone might have already developed. You find a React library that renders the clock and exposes a few props to customize its style and functionality (size, display seconds or not, frequency of update, etc). After a few tweaks, you have your component pretty similar to what you want, though not quite. Maybe you want to customize only the hours (make it bigger) or add a different color to the minutes. In the end, you wish you could just use the clock logic and style it exactly how you want.

This scenario is a great use case for Hooks. You can “hook” the “clock behavior” into a component and have the current time available. Then, you just use the time as you would use any variable declared in the scope of your component, the difference being that you don’t need to worry about it (in case you are curious, check the useClock hook that I developed and that illustrates this principle).

But before diving deeper into Hooks, let us understand the issues with the former solutions.

The world before Hooks

We have seen that React is great for reusing UI but that sometimes we might be interested only in the logic behind a component. Hooks are an answer to this demand. Let’s examine how we reused logic in the pre-Hooks era.

Higher-Order Components (HOC)

Probably the first pattern to be widely adopted by the React community on this matter, HOCs, solved the reusable logic problem using composition. Here is the idea: you develop a component which implements the logic and pass the computed values (for example, the current time) to the children, which have access to the values via props. Generically:

<ComponentWhichImplementsLogic>
  <ComponentWhichUsesLogic />
<ComponentWhichImplementsLogic>

Because <ComponentWhichImplementsLogic> is higher in the DOM order, we call them Higher-Order Components. HOCs conventionally start with the with prefix.

We can make them more legible by creating a function that accepts a component and returns this component with the injected logic:

ComponentWhichImplementsLogic(ComponentWhichUsesLogic)

Or, recalling our example:

withClock(MyComponent)

Considering that withClock is an HOC which implements the clock feature and exposes the current time, MyComponent would then access the current time via props. I won’t dive into the specifics of the development, but consider that a withClock component would store the current time in the state, add a setInterval in componentDidMount that increments the time at each interval, and does the proper cleanup when unmounted (calls clearInterval).

The first problem with this approach is that using multiple HOCs starts to become cumbersome. Suppose you also want your component to track the user’s current mouse position:

withMousePosition(withClock(MyComponent))

And you can certainly imagine why this is suboptimal if the number of injected logic grows. Also, it makes debugging and testing a pain. If your component misbehaves, detecting the flaw in a tree of HOC’s won’t be pleasant.

Render Props

When people started complaining about the issues of Higher-Order Components, a new pattern emerged: render props. You can read all about it in this very popular post by Michael Jackson. Basically, the component which implements the logic receives a render prop and calls it in its own render function, but now exposing the computed values:

<Clock render={props => <MyComponent {...props} />} />

This way, whatever props are returned by Clock, will be accessible to MyComponent. The render props pattern avoids some hard to debug issues such as name collision (suppose you accidentally override a prop or state name) and is somewhat less verbose, despite of the excess of JSX.

While the community seemed to adopt the render props pattern, composing logic is still suboptimal and some started to complain about the dreaded callback hell:

<Clock render={({time}) => (
  <Mouse render={({x, y}) => ( 
    <MyComponent time={time} x={x} y={y} />
  )} />
)} />

This becomes unreadable pretty quickly. Clearly, there is still much room for improvement. Next, we will see what a Hooks approach would look like.

The Hooks solution

While React definitely has alternatives for the reuse of logic, we have seen that it is suboptimal – particularly for composition (multiple logics being added to a single component can become too verbose, too hard to debug and too similar to callback hell).

Without further ado, consider how the former example could be written using hooks:

const MyComponent = () => {
  const [time] = useClock();
  const [x, y] = useMouseMove();
  // do something with those values
  return (
    <>
      <div>Current time: {time}</div>
      <div>Mouse coordinates: {x},{y}</div>
    </>
  );
}

That’s it! You simply declare your Hooks (in this case, useClock and useMouseMove) and have them instantly available inside your component. There is no extra work. Just plug it (hook) into your component and you are good to go.

Notice that we can use as many Hooks as we want. It is seamlessly composable and trivial to test (forget about a deep tree of nested components and weird inline JSX). And there is more: as you can see in the above example, it works with functional components, which reduces the verbosity of your component considerably (forget about using this or setting up constructors).

Indeed, with the advent of Hooks using class components are somewhat discouraged. According to the docs, there are just a few edge cases where classes have no Hooks equivalent, so it is very likely that you can start using Hooks right away.

Does that mean that you need to rewrite all your class components using Hooks? Definitely not. Hooks are a powerful solution and it is being well received by the community. However, by no means feel like you should do a major rewrite just because of that.

With that being said, let’s understand the main differences between classes and Hooks.

The Mental Model

One of the biggest distinctions between classes and Hooks is that Hooks have no lifecycles. For experienced React developers, this can take some time to click. We are used to thinking in terms of componentDidMount, componentWillUpdate, etc. But with Hooks things don’t work this way.

So the first thing you need to avoid is trying to map a given lifecycle to a particular use case with Hooks. This will only confuse you. Instead, just think that for Hooks any state or prop change calls the function again (recall your component is a function) with the updated values (state and props). For peace of mind, the docs give you a rough equivalency of certain lifecycles in the Hooks world.

One of the core contributors to React, Dan Abramov, wrote in his blog a piece on the differences between function and class components. Go check it out if you are curious about the internals.

Now that we understand that Hooks do not operate based on lifecycles (like classes do), let’s examine how the most common Hooks work.

useState

The first and simplest Hook is useState. Basically, it does exactly what you would expect: you declare a variable and a setter. Here’s how it looks:

import React, { useState } from 'react';

const Counter = () => {
  const [count, setCount] = useState(0);
  return (
    <div>
      <div>Current count: {count}</div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

As you can see, the hook returns an array with two elements and takes one parameter.

  • The first element of the array is the current value for this state;
  • The second element of the array is a setter that changes the value of this state;
  • The parameter used in the hook (in our example, 0) is the initial value of this state.

Naturally, you can add multiple states by adding multiple hooks:

const Counter = () => {
  const [count, setCount] = useState(0);
  const [step, setStep] = useState(1);
  return (
    <div>
      <div>Current count: {count}</div>
      <button 
        onClick={() => setCount(count + step)}>
        Increment by {step}
      </button>
    </div>
  );
}

Now recall that with Hooks every re-render is nothing else than the function being called again (in our example, Counter). But for every re-render, the state will be updated with its current value. This is the main difference between a state variable and a regular variable. Regular variables will always be re-defined. State variables will be derived from the previous state.

How do we trigger a re-render? Every time a prop or state variable change we trigger a re-render. This is done by invoking the setter, in our example through the click of the button. This is important to know so we can better understand the next Hook.

useRef

Sometimes we want to store a value in our component for future reference, but we don’t want to trigger a re-render (as this value isn’t expected to change often or has little or no UI impact). This would be the equivalent of having a class property:

this.myValue = value;

To address this case, useRef was introduced. Refs give us a way to store a variable in the scope of the function and preserve its value across renders. One typical case is to set a reference into an HTML component:

import React, { useRef } from 'react';

const FocusInput = () => {
  const inputRef = useRef(null);
  const onClick = () => inputRef.current.focus();
  return (
    <div>
      <input ref={inputRef} />
      <button onClick={onClick}>Focus</button>
    </div>
  );
}

Notice that useRef receives an initial value (in our example, null) and returns the ref variable. Whatever is stored in this ref can be accessed via myRef.current, so when we assign the ref of the input element to inputRef, inputRef.current holds the actual DOM element, allowing us to programmatically trigger focus.

In fact, useRef is a special case of useState, but one that we set its value directly instead of using a setter, which persists the value across renders without triggering a re-render.