React custom hooks

By creating custom hooks, we have a powerful tool to share state and logic between components beyond render props or higher-order components. As a result, we get small and reusable functions that are easy to maintain and remix. Just like the built-in hooks, such as useEffect, a custom hook should per convention start with 'use', eg. useClickOutside and is ideally put in its own files for easier reusability and maintenance (e.g. /components/hooks/useClickOutside.jsx).

A custom hook at its core is nothing but a function. It can take any number of arguments and return anything we want. It's important to note that two different components using the same hook won't share state. If you're reusing a hook in multiple places, all state and effects inside of it are fully isolated. With that in mind, let's look at some custom example hooks.

Handling click outside events in React

One great use case for a custom hook is a "click outside" handler that allows us to execute any logic when the user clicks outside a given element on the page. In the example below, we pass in a React ref and check if the event.target is inside the clicked element. In case the user clicked outside, we trigger the callback that's been passed as the second parameter. Lastly, the return function inside our hook is used to unbind the click listener.

import React, { Ref, useEffect } from "react";

function useClickOutside(ref, callback) {
    useEffect(() => {
        function handleClickOutside(event) {
            if (ref.current && !ref.current.contains(event.target)) {
                callback()
            }
        }
        document.addEventListener("mousedown", handleClickOutside)
        return () => {
            document.removeEventListener("mousedown", handleClickOutside)
        }
    }, [ref])
}

export { useClickOutside }

Let's assume we separated the hook from our other components, we can now import it wherever we need to and use it as follows. In this example, we check if the user clicked outside a modal and then change its state via setModalVisible — what we do after a successful callback is of course dependent on what we're trying to achieve.

import { useClickOutside } from "./components/hooks/useClickOutside"
const modalElement = useRef(null)

useClickOutside(modalElement, () => setModalVisible(false))

return (
    <>
        {modalVisible && (
            <div className="backdrop">
                    <div className="login-modal" ref={modalElement}>
[...]

Making use of reusability

The biggest strength by far when it comes to hooks is their reusability — Not only in a single project but across projects that rely on React. Because hooks can be used by any other component, it is easy to take a component from one project and use it again in another. Our example above would work the same way in two vastly different projects and that's the case for all well-written hooks. That's also why it can be useful to have a few custom hooks up your sleeve when you need them and don't have to reinvent the wheel.

That being said, here's a great list of custom hooks that are worth looking at or saving for later.

Further reading

The React docs have a handy page with all the rules and conventions we should follow when creating our own hooks. There we can also find a great example of extracting a custom hook that was simply a function inside a component before.

JavaScript
React