Write Your First CUSTOM React Hook

react
frontend

When it comes to logic within React components, a lot of it can be covered by the hooks provided by React but you will notice that as your project grows in size and more custom logic is needed, a lot of that logic is reused in a bunch of different places. Creating a custom React hook is the perfect way to encapsulate that reusable logic in a function that can be shared across multiple components. If you find yourself duplicating code between components or want to abstract complex behaviour into a more manageable and reusable piece of code, then you should definitely consider writing a custom hook.

A few weeks ago, we took a look at the most commonly used hooks that React comes with, which you can read about here. We can continue with the same example we used there in order to demonstrate how custom hooks work, by building a counting hook. It will keep track of its own state while offering the ability to increment, decrement or reset the count.

Use Cases

In case you are still not sold on the idea of these custom hooks, here are some of the most common uses cases for why you would want to create them:

  • Reusability — complex logic can be encapsulated in order to be resued in multiple components
  • Side Effects — side effects like data fetching, authentication or animations can all be managed within a custom hook
  • State Management — state can easily get out of hand but with custom hooks it can be managed in a more focused and granular way
  • Third-Party Libraries — the functionality of third-party library integrations can also be encapsulated to be more reusable
  • Testing — custom hooks can be tested independently allowing for isolating logic into small testable units

The Hook

Creating a hook is extremely simple. We first need to create the file which in our case we will call useCounter.js. The first thing to point out here is that when it comes to naming hooks, the convention is to prefix them with the word use in order to make it clear that it is a hook.

Inside of our useCounter.js file we need to define our hook function:

export default function useCounter(initialValue = 0) {}

You will notice that our hook takes in one parameter: initialValue. This is simply just the value we want our counter to start at. Next, we need to import the useState hook. This might be slightly surprising when writing your first hook but yes, we can use other hooks within our hook. With useState, we will be able to keep track of our counter’s value.

import { useState } from 'react';
 
export default function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);
}

Now that we have the value of our counter figured out, we can work on the custom functionality that we want to get our of our hook. This is the functionality that was repeating across multiple components and so, by placing it here, it can easily be reusable.

const reset = () => setCount(initialValue);
 
const increment = () => setCount(count + 1);
 
const decrement = () => setCount(count - 1);

With these actions, we can now increment, decrement or reset or counter. All we have left to do is put it all together and return these actions from our hook.

import { useState } from 'react';
 
export default function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);
 
  const reset = () => setCount(initialValue);
 
  const increment = () => setCount(count + 1);
 
  const decrement = () => setCount(count - 1);
 
  return {
    counter,
    reset,
    increment,
    decrement,
  };
}

Usage

Using the hook is pretty straight forward, just like the other hooks we are used to. We simply just have to import it in the component file we want to use it in, call it within the component and use it’s values wherever needed. Let’s look at an example component and how it would use the values from our hook.

import React from 'react';
import 'useCounter' from './useCounter';
 
export default function Counter() {
  const { count, reset, increment, decrement } = useCounter(2);
 
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>increment</button>
      <button onClick={decrement}>decrement</button>
      <button onClick={reset}>reset</button>
    </div>
  );
}

And that is all! Our Counter component is now using the custom hook we created. Our counter is initialized with the value of 2 and we can increment, decrement or reset it. The thing to keep in mind though, as project sizes increase, you can run into conflicting names between the functions you are extracting from all the custom hooks. In most of these cases I ended up just leaving the state as an object, looking something like:

export default function Counter() {
  const counter = useCounter(2);
}

This might not seem as clean having to call counter.increment etc., but when dealing with larger components that have a lot of moving pieces, it can actually make the code a lot more readable knowing what hooks functionality is being called.

Conclusion

With a simple hook, we were able to drastically increase the reusability of our code. Now, whenever we need a counter in one of our components, we are just one import away from one.

If you found this useful, check out my other articles and my YouTube Channel where I cover a lot of the same topics but in video format.