snippet
useOnClickOutside
snippet
useOnClickOutside
Last updated January 21st, 2023
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.
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.
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;
};
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>
)}
</>
);
};