Logo
Cover image

Custom React Hooks: useCounter

Posted on 10/10/2021

reactjavascriptprogramming

In the last episode of the Custom React Hooks series, we have implemented the useBoolean hook. Today, we will create a not more complicated one: useCounter. As its name suggests, this hook will help us manage the state of a counter thanks to utility methods. Ready? Let's jump right into it.ย ๐Ÿš€

Motivation

As always, let's see why would you want to implement this hook. Imagine you are building an e-commerce website. Whenever a customer wants to buy an item, he should be able to select the quantity. This is how the user interface might look like (let's forget about the style):

UI

And the source code of the Cart component might look like this:

1const Cart = () => {
2  const [quantity, setQuantity] = useState(0);
3
4  return (
5    <div className="Cart">
6      <h1>My Cart</h1>
7      <Item
8        label="My awesome item"
9        quantity={quantity}
10        onIncrement={() => setQuantity((q) => q + 1)}
11        onDecrement={() => setQuantity((q) => q - 1)}
12        onReset={() => setQuantity(0)}
13      />
14    </div>
15  );
16}

Of course, this example is extremely simplified in order to only focus on what matters the most in this article.

The above component could be simplified by getting rid of those awful arrow functions. Moreover, as we are probably going to use such a counter logic in other parts of our app, we should extract it in a separate hook to avoid code duplication. That's why we will implement a custom useCounter hook. Let's now get our hands dirty.ย ๐Ÿ˜Ž

Implementation

Our hook will have a state to hold the counter value. It will also have 3 methods to update it: increment, decrement, and reset. With this in mind, we can create the hook in the following way:

1const useCounter = (initialValue) => {
2  const [value, setValue] = useState(initialValue);
3
4  const increment = () => setValue(c => c + 1);
5  const decrement = () => setValue(c => c - 1);
6  const reset = () => setValue(initialValue);
7
8  return { value, increment, decrement, reset };
9};

Now, some of you might wonder why the 3 exported methods are not wrapped into the useCallback hook, which prevents from recreating them every time the component is re-rendered (more info here). In our case, the component is so simple that calling useCallback 3 times to improve performance could have the opposite effect. This hook should be used when you know your function will be used in a more complex component, for instance one that holds a big list of items, each one using your function as an event handler (such as onClick).

For more information, don't hesitate to have a look at this article from Dmitri Pavlutin, which goes into details about why, when and how to use the useCallback hook. The same applies for useMemo and useRef (except when used as a reference to an element).

That being said, our new custom hook is now ready to use!ย ๐Ÿฅณ

Usage

Let's get back to our e-commerce website example. Given that we just created this brand new custom hook, the Cart component can now be simplified as follows:

1function Cart() {
2  const quantity = useCounter(0);
3
4  return (
5    <div className="Cart">
6      <h1>My Cart</h1>
7      <Item
8        label="My awesome item"
9        quantity={quantity.value}
10        onIncrement={quantity.increment}
11        onDecrement={quantity.decrement}
12        onReset={quantity.reset}
13      />
14    </div>
15  );
16}

We've got rid of all the arrow functions, which resulted in a cleaner and more readable code.

Improvement ideas

If you want to go further, here are some ideas for improvement to enhance the useCounter hook. Don't hesitate to try implementing one or more of these ideas, so that you can practice on your own.

  • Adding an increment (and decrement) step: counter.increment(step)
  • Adding a min (and max) value: useCounter({ min: 0, max: 10, initial: 0})
  • Manually set the counter value: counter.set(value)

Wrapping Up

With this new custom hook, we now have a new string to our bow. So far, the hooks we are creating are pretty simple, and can even be considered superfluous by some. In the next articles, we will start implementing more complex hooks to really help us simplify our components code and avoid code duplication.


Source code available on CodeSandbox

Copyright ยฉ 2022 Ludovic CHOMBEAU