Get Hooked on Writing Hooks in React

A simple demonstration of how you can make your own Hooks to handle state logic in your React components.

Get Hooked on Writing Hooks in React

Hooks are a really popular feature of React as they allow you to simplify your component logic by doing away with complicated class-based components in favour of functional components. They make testing your applications a breeze, and also make it easier to share pieces of logic amongst your components as well as with the rest of the community.

A Hook is just a function that's able to use state and lifecycle events from a functional component. Popular Hooks that you may have come across are useState and useEffect which come from the React library itself. There are also Hooks provided through other libraries like useColor and usePalette in color-thief-react (which I covered in this post). But as I'm going to show you in this article, there's nothing difficult about Hooks and it's super simple to roll your own.

If you're unfamiliar with the concept of Hooks or just need a refresher, check out the writeup from the React team here. It's pretty comprehensive and easy to follow.

What Makes a Hook a Hook?

Hooks are super simple to create, and are more of a design pattern than anything else. There aren't necessarily any "rules", however sticking to convention will help provide consistency for your application.

A Hook typically has the following structure:

  1. a function name prefixed with the word "use" (i.e. useState, useYourBrain)
  2. 0+ parameters to help initialize the state
  3. 1+ usages of React's useState (if you don't have this, you probably don't need to create a Hook)
  4. a return value that at least includes the current state

Aside from that, you can add anything else you need inside of a Hook to make it fulfill whatever function you'd like. Make sure you keep things relevant and avoid having the Hook do too many things.

Here is a simple example of a Hook:

const useBusyStatus = () => {
  const [isBusy, setIsBusy] = useState();

  function beBusy() {
    setIsBusy(true);
  }

  function beAvailable() {
    setIsBusy(false);
  }

  return [isBusy, beBusy, beAvailable];
}

This Hook would help to manage and monitor someone's busy status, and lets you hook into its state via isBusy to see whether they're busy or not. It also returns two callbacks, beBusy and beAvailable to provide some control to manipulate the isBusy state.

This is cool and everything, but I think the usefulness of Hooks would be better demonstrated if we saw a before and after example where there is some more complex logic involved.

How to Extract Existing Logic into a Hook

So now you're convinced it's worth the effort to Hook-ify some of your component's state logic, but perhaps not quite sure how to go about it. Let's go through an example together doing exactly that.

Say you have a component that displays some content that only an authenticated user should be able to see. You determine whether a user is authenticated or not via a call to some API endpoint. If they're not authenticated, you redirect them to the login page so they can authenticate themselves.

Below is what such a component could look like:

const ComponentWithSensitiveContent = () => {
  const history = useHistory();
  const authAPI = new AuthAdapter();

  const [isAuthenticated, setIsAuthenticated] = useState();

  function onAuthenticated() {
    setIsAuthenticated(true);
  }

  function goToLogin() {
    history.push("/login");
  }

  useEffect(() => {
    authAPI.isAuthenticated()
      .then(onAuthenticated, goToLogin)
  });

  return (
    <div>
      {
        isAuthenticated &&
        <p>I am sensitive content</p>
      }
    </div>
  );
}

This doesn't look to crazy for now, but imagine that this authentication logic is something we'd like to use in several components. This would quickly become quite cumbersome and we'd need to find a way to package it into some form that's easily reusable.

So let's create our own hook, useAuthenticationStatus, and extract all the auth-related logic into it.

const useAuthenticationStatus = () => {
  const history = useHistory();
  const authAPI = new AuthAdapter();

  const [isAuthenticated, setIsAuthenticated] = useState();

  function onAuthenticated() {
    setIsAuthenticated(true);
  }

  function goToLogin() {
    history.push("/login");
  }

  useEffect(() => {
    authAPI.isAuthenticated()
      .then(onAuthenticated, goToLogin);
  });

  return isAuthenticated;
}

After pulling all of the authentication logic out, we are able to really simplify our original component.

const ComponentWithSensitiveContent = () => {
  let isAuthenticated = useAuthenticationStatus();

  return (
    <div>
      {
        isAuthenticated 
        && <p>I am sensitive content</p>
      }
    </div>
  );
}

Now we can re-use this stateful logic in all other types of components simply by pulling in the useAuthenticationStatus Hook. Testing also becomes a lot easier as we only need to worry about mocking our Hook in our components instead of all of the other imports (i.e. the auth endpoint) that don't relate to the component itself.

Wrap up...

So now you know the power of Hooks and what they can do for your React applications. This design pattern will help you clean up your components, share reusable logic, and make your components more testable.

If you want to play around with any of the code from the snippets above, check out this Sandbox.

Thanks for reading, now go build some stuff!