---
title: Custom React Hooks and When to Use Them
teaser: 'Custom hooks are one of the most underused React abstractions. Let''s explore
  when it makes sense to use them compared to other abstractions.

  '
tags: react,javascript,web
author: Stephen Hanson
published_on: 2023-01-31
---

React hooks are functions that let you use and interact with state in React
function components. React comes with some built-in hooks, the most commonly
used ones being `useState`, `useRef`, and `useEffect`. The former two are used
to store data across renders, while the latter is used to execute side effects
when that data changes.

We can also build our own hooks using the built-in hooks as building blocks.
These are often referred to as "custom hooks" to differentiate them from the
built-in hooks. In my experience, **custom hooks are the most underused React
abstraction**. Developers who are newer to React can struggle to understand how
to build custom hooks or when to use them. This post will focus on answering
those questions.

## Built-in hooks

As a quick refresher on hooks, here's an example of a React component that keeps
track of a count and logs to the console when the count changes:

```tsx
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log(`Count changed to ${count}`);
  }, [count]);

  return (
    <div>
      The count is: {count}.
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}
```

The component above stores `count` as state using the `useState` hook and also
logs the count anytime it changes by using the `useEffect` hook.

## What are custom hooks

The React documentation on [building your own
hooks](https://reactjs.org/docs/hooks-custom.html#extracting-a-custom-hook)
defines custom hooks in a simple way:

> A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks.

That’s it! If you have code in a component that you feel would make sense to
extract, either for reuse elsewhere or to keep the component simpler, you can
pull that out into a function. If that function calls other hooks, like
`useEffect`, `useState`, or maybe another custom hook, then your function is
itself a hook, and, by convention, should be given a name that starts with “use”
to make it clear that it is a hook.

If hooks are so similar to regular functions, you might wonder why we even have
the "hook" concept. The reason we need this concept is because hooks are
special. They are functions that also have state that is persisted under the
hood by React across calls. Because of this, there are [rules of
hooks](https://reactjs.org/docs/hooks-rules.html) that must always be followed
so React doesn't get confused. The `use...` naming convention helps us identify
which functions are hooks so we can be sure to follow the rules.

Following are some examples of when it might make sense to create a custom hook.

## Reusable custom hooks

Most React developers are familiar with extracting reusable functionality into
components or functions but are sometimes not as comfortable extracting code
into hooks. If we have code we would like to extract from a component, a custom
hook might be the proper extraction if the following conditions are met:

- The extracted code has no JSX output (if it does, then you should create a component instead)
- AND the extracted code includes calls to other hooks (if it doesn't, then create a regular function instead)

Let's take a simple example of a reusable hook. Say in our app, we want to
change the document title every time we render a new page. We might have this
code in many of our components:

```jsx
function HomePage {
  useEffect(() => {
    document.title = 'Home'
  }, [])

  return <div>Home Page...</div>
}
```

We could simplify this a bit by extracting the title logic into a hook:

```jsx
function useTitle(title) {
  useEffect(() => {
    document.title = title;
  }, [title]);
}
```

Now, our home page looks like this:

```jsx
function HomePage {
  useTitle('Home')

  return <div>Home Page...</div>
}
```

This was a simple example, and it likely would have been fine to not create the
hook in this case, but as functionality gets more complex or is used in more
places, it becomes more and more useful to extract the behavior.

Or, here's another reusable custom hook that can be useful:

```ts
/**
 * Just like `useState`, but it keeps track of the previous value and returns it
 * in the array.
 * Usage:
 *   const [value, previousValue, setValue] = useStateWithPrevious('initialValue')
 */
function useStateWithPrevious(initialValue) => {
  const reducer = (state, value) => ({
    value,
    previousValue: state.value,
  });

  const [{ value, previousValue }, setValue] = useReducer(reducer, {
    value: initialValue,
  });

  return [value, previousValue, setValue];
};

function App() {
  const [name, previousName, setName] = useStateWithPrevious('')
  // now we always have access to the previous value
}
```

For some more custom hook inspiration, I recommend checking out
[react-use](https://github.com/streamich/react-use).

## Non-reusable custom hooks to extract functionality

Just like with components, sometimes it can be useful to create a custom hook
even if it isn't reusable as a way to extract functionality to make the parent
component easier to understand.

When assessing if it makes sense to extract a non-reusable hook, I use the same
criteria I would use for any other extraction:

- Is the parent code more understandable with the abstraction?
- Is the abstraction isolated enough to make sense on its own?

If the answer to either of the above is no, then it does not seem that the hook
is a clean abstraction, and the code is probably easier to understand with the
hook inlined back into the component. Even if both answers are yes, still ask if
the abstraction is worth the extra complexity created by this new layer.

### Determining how much to extract

It's not always clear _where_ best to define your abstraction. This choice can
affect if your new abstraction should be a hook vs. a component vs. a regular
function.

Consider this contrived component where we access the user from a global store
(like Redux or React Context) and then display a message like "Hello, Dr. Jane
García". Let's also assume our app needs to display the same message in a few
different places, so there's a desire to extract some of this functionality for
reuse.

Here's the original component with no extraction:

```jsx
function MyComponent({ displayTitle }) {
  const user = useSelector((state) => state.user);

  const formattedName = _.compact([
    displayTitle ? user.title : null,
    user.firstName,
    user.middleName,
    user.lastName,
  ]).join(" ");

  return (
    <>
      <h1>Hello, {formattedName)}</h1>
      <p>Some text</p>
    </>
  );
}
```

The simplest extraction we could make is to pull out the logic for formatting
the user's name into a separate function:

```jsx
function formatUserName(user, { displayTitle }) {
  return _.compact([
    displayTitle ? user.title : null,
    user.firstName,
    user.middleName,
    user.lastName,
  ]).join(" ");
}

function MyComponent({ displayTitle }) {
  const user = useSelector((state) => state.user);

  // extracted logic to a function
  const formattedName = formatUserName(user, { displayTitle });

  return (
    <>
      <h1>Hello, {formattedName}</h1>
      <p>Some text</p>
    </>
  );
}
```

Another option would be to extract the `useSelector` call as well, which
means we are now creating a hook instead of a regular function (remember: if
your function calls a hook, then your function _is_ a hook):

```jsx
// extracted everything, including the global store access, so this is a hook
function useFormattedUserName({ displayTitle }) {
  const user = useSelector((state) => state.user);
  const formattedName = _.compact([
    displayTitle ? user.title : null,
    user.firstName,
    user.middleName,
    user.lastName,
  ]).join(" ");

  return formattedName;
}

function MyComponent({ displayTitle }) {
  const formattedName = useFormattedUserName({ displayTitle });

  return (
    <>
      <h1>Hello, {formattedName}</h1>
      <p>Some text</p>
    </>
  );
}
```

A third option, that is even heavier-handed would be to extract this into a
component:

```jsx
function UserGreeting({ displayTitle }) {
  const user = useSelector((state) => state.user);
  const formattedName = _.compact([
    displayTitle ? user.title : null,
    user.firstName,
    user.middleName,
    user.lastName,
  ]).join(" ");

  return <h1>Hello, {formattedName}</h1>;
}

function MyComponent({ displayTitle }) {
  return (
    <>
      <UserGreeting displayTitle={displayTitle} />
      <p>Some text</p>
    </>
  );
}
```

The examples above are fairly contrived, but they demonstrate some of the nuance
around choosing the right abstraction point. There are always multiple ways to
build abstractions.

As we'll discuss in the next section, it's usually best to create smaller
abstractions, even at the risk of a little duplicated code. So in this case,
it's probably best to just create the `formatUserName` function and allow a bit
of duplication with the `useSelector` call and JSX output in the various places
we display a greeting to the user.

If we had instead created the `<UserGreeting />` component, a larger abstraction
that includes displaying/styling, then we would be forced to modify that
abstraction anytime we have a need to display the user's name with a different
style, salutation, or HTML tag. This would introduce an undesirable coupling
between different parts of the codebase as that component changes.

## The cost of abstractions

As we discussed in the last section, abstractions don't come for free. They have
downsides such as creating coupling and introducing extra layers and
indirection. Sometimes the best answer is to forego the abstraction and have
some duplication.

Over time, requirements change. When we update an abstraction, we have to
consider how an update affects all of the call sites. This slows us down and
also makes it easy to introduce bugs.

In addition to creating coupling, abstractions also add extra layers and
indirection in an app that can make the code harder to understand. Our brains
can only hold onto a few layers at a time in memory, so as complexity increases,
our understanding of the functionality decreases.

[The React
documentation](https://reactjs.org/docs/hooks-custom.html#useyourimagination)
advises the following, regarding abstractions:

> Try to resist adding abstraction too early. Now that function components can
> do more, it’s likely that the average function component in your codebase will
> become longer. This is normal — don’t feel like you have to immediately split it
> into Hooks. But we also encourage you to start spotting cases where a custom
> Hook could hide complex logic behind a simple interface, or help untangle a
> messy component.

If you aren't sure if an abstraction needs to be added, it probably does not! If
you are unsure how best to shape an abstraction, consider holding off until the
needs are clear. As [Sandi Metz
says](https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction), "duplication
is far cheaper than the wrong abstraction".

For further information on downsides of using too many (or wrong) abstractions,
I recommend watching Dan Abramov's talk, [The Wet
Codebase](https://www.youtube.com/watch?v=17KCHwOwgms).

## Conclusion

Custom hooks are a useful abstraction. Understanding them and when to use them
is key to building a maintainable React application. And while components,
hooks, functions, and other patterns each have their place in the developer's
toolkit, we should also be judicious when creating abstractions, understanding
that they create coupling and additional layers and indirection.
