Write Custom Hooks to Clean Up Your Code

By Dave Ceddia updated

Custom Hooks tidy up your code! (they're easier than you think)

React hooks make it easy to add a single piece of state to a component. But it’s also a breeze to write your very own custom hooks, just by moving the hooks-related code into a function.

Say you need a boolean true/false flag to keep track of whether a bit of spoiler text is shown or hidden. You could wire it up like this…

import React, { useState } from 'react';

function SpoilerAlert({ spoilerText }) {
  const [isVisible, setVisible] = useState(false);

  return (
    <div>
      <button onClick={() => setVisible(!isVisible)}>
        {isVisible ? 'Hide' : 'Show'}
      </button>
      {isVisible && <span>{spoilerText}</span>}
    </div>
  );
}

The useState here doesn’t do a great job of expressing the intent of that state though. Don’t get me wrong – I’m not saying it’s bad, just that I think it could be better. Wouldn’t it be cool if it looked like this instead?

import React, { useState } from 'react';

function SpoilerAlert({ spoilerText }) {
  // useToggle doesn't exist yet, but what if it did?
  const [isVisible, toggleVisible] = useToggle(false);

  return (
    <div>
      <button onClick={toggleVisible}>
        {isVisible ? 'Hide' : 'Show'}
      </button>
      {isVisible && <span>{spoilerText}</span>}
    </div>
  );
}

It’s a small change, but it reads more nicely. The onClick={toggleVisible} prop, especially, is more succinct and clear than onClick={() => setVisible(!isVisible)}.

Let’s write the useToggle hook.

Custom Hooks are Just Regular Functions

You can bundle up any chunk of hooks logic into a function to make your very own fancy custom hook! Just make sure your function name starts with “use”.

Once you see how easy is is to write a custom hook, I think you’ll start to see uses for them all over your app.

Our useToggle hook is mostly just a call to useState, but instead of handing back a general-purpose “setter” function, we’re gonna create a purpose-built “toggler” function and return that instead.

We’re wrapping up the setter logic to make it crystal clear to whoever uses this hook that the value is meant to be toggled.

function useToggle(initialValue) {
  const [value, setValue] = useState(initialValue);

  const toggleValue = () => setValue(!value);

  return [value, toggleValue];
}

I think of little hooks like this as “quality of life” hooks. Did we desperately need to create this hook? Was the code really that bad before? No. It was fine. But this little bundle of 5 lines of code makes it finer.

Keep custom hooks like this in a file (say, hooks.js?), and next time you need to create a toggleable value, just import { useToggle } from './hooks' and you’re good to go!

Another Example: useBoolean

Just to hammer the point home, let’s see one more simple custom hook – another variant on a boolean value.

This one is meant for a value you need to explicitly turn ON and OFF, instead of toggle. Imagine a modal dialog with only one way to open it, and a few ways to close it (X button, Escape key, Cancel button, after a request succeeds, …).

You might initially rely on useState to create a boolean:

const [isVisible, setVisible] = useState(initialValue);

Then you could define a couple helpers, and pass one of these wherever you need a callback function (like for an onClick handler or similar).

const showModal = () => setVisible(true);
const hideModal = () => setVisible(false);

I’d say a function called showModal is clearer than () => setVisible(true). But we can go one step further, and bundle that logic into a custom hook:

function useBoolean(initialValue) {
  const [value, setValue] = useState(initialValue);

  const setTrue = () => setValue(true);
  const setFalse = () => setValue(false);

  return [value, setTrue, setFalse];
}

Again it’s all about making the intent clear, and tidying up the code a bit. All we’ve done is move the state and the helper callbacks into a new function, and now we can use it in a component like this:

const [isVisible, showModal, hideModal] = useBoolean(initialValue);

Now you have a reusable bit of state and helper functions! The next time you need a flag to show/hide a sidebar, a tooltip, or whatever else, just import useBoolean.

Look for little opportunities to create custom hooks in your own code. How can you make your existing code more expressive?

The more naturally the code reads, the easier it will be to maintain.