Writing Custom Hooks in React

Tags

An assortment of fishing lures

You’ve probably heard of React Hooks at this point, and hopefully, you’re familiar with the basics like useState and useEffect. But, did you know that React gives us a way to write our own, custom Hooks? Let’s take a look at the “what,” “how,” and “why” of writing our own Hooks.

Building your own Hooks lets you extract component logic into reusable functions.

What is a “Custom Hook?”

A custom Hook is really just a reusable function that follows the rules of Hooks:

  • Starts with “use”, i.e. useLocalStorage or useEventListener
  • Only calls other Hooks at the top level

It’s important that custom Hooks follow the useHook naming convention because it allows React tooling to ensure you’re following all the rules of Hooks and tells other developers what they’re dealing with.

Calling Hooks at the “top level” just means that we’re not doing it inside of loops, conditions, or nested functions. Hooks need to be called in the same order each time a component renders, so our calls have to stay outside of any program flow controls, like if statements.

Why are Custom Hooks Useful?

Custom Hooks should be used when you notice yourself doing the same thing over and over again in different components. I know what you are thinking: “isn’t that just what functions are for? Why do we need Hooks to do that?” Well, there’s one more Hook rule:

Only Call Hooks from React Functions

We’ve already seen that React is picky about when it’s okay to call Hooks. Normally we can only call them at the top level of function components (and I just told you that you can’t call them in nested functions). But, there’s an exception to that: custom Hooks are React Functions, so that means that we can call other Hooks from inside them.

How to Use Custom Hooks

Let’s take a look at an example.

Suppose we have multiple components that need to be aware of mouse position. We’d probably do something like this:

const Tracker = () => {
  const [pos, setPos] = useState({ x: 0, y: 0 });

  useEffect(() => {
    const listener = document.addEventListener('mousemove', ({ clientX, clientY }) => {
      setPos({ x: clientX, y: clientY });
    });

    return (() => {
      document.removeEventListener('mousemove', listener);
    });
  }, []);

  return (
    <div>
      X: {pos.x}
      Y: {pos.y}
    </div>
  );
}

We’d set up a useEffect to attach and remove the event listeners appropriately and store some event data in a little bit of state. But doing this over and over in each component gets cumbersome pretty quickly. The good news? We can extract that logic into a custom Hook!

const usePosition = () => {
  const [pos, setPos] = useState({ x: 0, y: 0 });

  useEffect(() => {
    const listener = document.addEventListener('mousemove', ({ clientX, clientY }) => {
      setPos({ x: clientX, y: clientY });
    });

    return (() => {
      document.removeEventListener('mousemove', listener);
    });
  }, []);

  return pos;
}

And now we can reuse it wherever we want!

const Tracker = () => {
  const { x, y } = usePosition();

  return (
    <div>
      X: {x}
      Y: {y}
    </div>
  );
};

const App = () => {
  const { x } = usePosition();
  return (
    <div style={{ background: `hsl(${x}, 100%, 50%)`, color: 'white' }}>
      <Tracker />
    </div>
  );
};

The next time you are using React–and find yourself doing the same thing over and over again in different components–consider trying custom Hooks to save yourself some time, effort, and headspace!

DockYard is a digital product agency offering custom software, mobile, and web application development consulting. We provide exceptional professional services in strategy, user experience, design, and full stack engineering using Ember.js, React.js, Ruby, and Elixir. With a nationwide staff, we’ve got consultants in key markets across the United States, including San Francisco, Los Angeles, Denver, Chicago, Austin, New York, and Boston.