snippet

useOnClickOutside

import { useRef, useEffect } from "react";

const useOnClickOutside = (handler) => {
  const refNode = useRef(null);
  const saved_callback = useRef(handler);

  useEffect(() => {
    saved_callback.current = handler;
  }, [handler]);

  useEffect(() => {
    const listener = (event) => {
      const element = refNode.current;
      if (!element || element.contains(event?.target || null)) return;
      saved_callback.current();
    };

    document.addEventListener("mousedown", listener);
    document.addEventListener("touchstart", listener);

    return () => {
      document.removeEventListener("mousedown", listener);
      document.removeEventListener("touchstart", listener);
    };
  }, [handler]);

  return refNode;
};

The hook situation

Let's say a user has just opened a modal, and we want to close that modal anytime the user clicks outside of it. This handy hook, useOnClickOutside provides a very simple method for us to accomplish that goal! To be specific, this hook detects whenever a mouse click has occurred outside of a specific element. The hook then calls a handler function to take a specified action.

The hook sets up a reference object using the useRef hook so that we can get a reference to a react node/ HTML element. We then create a listener function that checks if the current element contains the event that has just occurred. Specifically, we are checking if a mouse-down event has occurred inside the element the reference object is attached to. If this is the case, then we return out from the function, otherwise, we call our handler function.

We then set up an event listener to listen for mouse-down events and we pass our listener function as the callback. Note, that we also set up a listener for touch events, therefore, this hook also works with touch devices. Finally, we clean up our useEffect by removing the event listeners and then return the reference object.

How to use the useOnClickOutside hook

import useOnClickOutside from "./useOnClickOutside";

const App = () => {
  const refNode = useOnClickOutside(() => setIsOpen(false));

  const [isOpen, setIsOpen] = useState(false);

  return (
    <>
      <button onClick={() => setIsOpen(true)}>Open Modal</button>

      {isOpen && (
        <Modal ref={refNode}>
          <p>I am a paragragh inside of a modal</p>
        </Modal>
      )}
    </>
  );
};

useOnClickOutside takes in a callback handler function, in this case, it's a callback function that sets the isOpen state to false. The hook also then returns a reference object which is attached to an HTML element.

A note on the callback function

In some cases it may be good to memozie the callback function that is passed into the useOnClickOutside hook, so as to improve perferomance.

More information about when it is nessesary to memozie a function/value can be found at the react docs.

Last updated January 21st, 2023