Detect A Click Outside An Element Using React Hooks
Tony Pettigrew / October 04, 2022
4 min read • ––– views
Table of Contents:
Today we will be creating a hook to detect if a click happens outside of the component that the hook is called in. The use case for this, most of the time, is dynamically visible components such as modals, drawers and menus. If you get lost along the way, here is the example repo.
Planning It Out
As always, the first step is to create a plan and think about how to accomplish detecting a click outside of an element the React way:
- Since we will need to detect a DOM element, immediately, I know that we will need to use a reference (or ref) to the component.
- Since this click can occur anywhere, we will need to listen for it on the document body.
- To make the hook work on mobile, we'll check for both mouse and touch events.
- The component will need to be mounted before we can add event listeners so we'll need to utilize the useEffect hook to run our setup code.
- We'll also need to pass in a handler callback which will be called when our click outside condition is met.
- When the component unmounts we'll need to remove the event listers.
Writing The Hook
Now that we have an outline of what needs to happen, let's start writing the hook. We'll begin with the args.
Ref and Callback Args
The hook will need access to the element reference and function to call when the click outside condition is met.
import { RefObject } from "react";
type Event = MouseEvent | TouchEvent;
export const useOnClickOutside<T extends HTMLElement = HTMLElement> = (
ref: RefObject<T>,
callback: (event: Event) => void,
) => {
}
Setup Event Listeners
Now that we have access to the DOM element and callback function, we need to set up the event listeners as soon as the element mounts.
import { RefObject, useEffect } from "react";
type Event = MouseEvent | TouchEvent;
export const useOnClickOutside<T extends HTMLElement = HTMLElement> = (
ref: RefObject<T>,
callback: (event?: Event) => void,
) => {
// We'll address the logic of the event handler next
const listener = (event: Event) => {}
useEffect(() => {
// Add event listeners on mount
document.addEventListener("mousedown", listener);
document.addEventListener("touchstart", listener);
// Remove event listeners on unmount
return () => {
document.removeEventListener("mousedown", listener);
document.removeEventListener("touchstart", listener);
};
})
}
Event Handler
The logic for the event handler (function called listener) will check the event parameter of listener to see if it contains our DOM element ref, if so, we return from the function. If it does not, we will call the callback function.
import { RefObject, useEffect } from "react";
type Event = MouseEvent | TouchEvent;
export const useOnClickOutside = <T extends HTMLElement = HTMLElement> (
ref: RefObject<T>,
callback: (event: Event) => void,
) => {
const listener = (event: Event) => {
const el = ref?.current;
if (!el || el.contains(event?.target as Node)) {
return;
}
callback(event);
}
useEffect(() => {
// Add event listeners on mount
document.addEventListener("mousedown", listener);
document.addEventListener("touchstart", listener);
// Remove event listeners on unmount
return () => {
document.removeEventListener("mousedown", listener);
document.removeEventListener("touchstart", listener);
};
})
}
How To Use
To use the hook you will also need to use React's useRef
hook to capture the DOM element and pass it to our hook.
import { useRef } from "react";
import { useOnClickOutside } from "../hooks/useOnClickOutside";
export const ClickOutside = () => {
const ref = useRef(null);
// Will be called when the click outside condition is met.
const onClickOutside = () => {
console.log("Click Outside!");
};
useOnClickOutside(ref, onClickOutside);
return (
<div
ref={ref}
style={{
width: 200,
height: 200,
background: "blue",
}}
/>
);
};
Usually the callback will either close the modal, drawer or menu. Please feel free to use this in your projects!