Build a custom React hook that detects clicks landing outside a particular element. This is the machinery behind dropdowns, modals, popovers, and context menus: open the panel, and the next click anywhere except on the panel itself should close it. useClickOutside(handler) returns a ref you attach to the element you want to protect; when a mousedown fires anywhere outside that element, your handler runs. Clicks on the element — or on anything nested inside it — must not trigger it.
function useClickOutside(
handler: (event: MouseEvent) => void, // called on an outside click
): React.RefObject<HTMLElement>; // attach to the protected element
The hook returns a single ref. Put it on the element with <div ref={ref}>; the handler fires only for clicks that land outside that subtree.
function Dropdown() {
const [open, setOpen] = useState(false);
const ref = useClickOutside(() => setOpen(false));
return (
<div ref={ref}>
<button onClick={() => setOpen((o) => !o)}>Menu</button>
{open && <ul>{/* options */}</ul>}
</div>
);
}
// Clicking the button or any option keeps the menu open;
// clicking anywhere else on the page closes it.
// A click on the protected element or a descendant of it is "inside" —
// the handler does NOT run. Only clicks elsewhere count as "outside".
const ref = useClickOutside(onOutside);
// mousedown on ref.current → onOutside NOT called
// mousedown on a child of it → onOutside NOT called
// mousedown anywhere else → onOutside called
Node.contains — not an identity check against the element — so nested clicks are recognized.document. A single listener on document sees every click on the page; from event.target you decide whether it landed inside or outside the protected element.ref.current is null — calling the handler should be skipped, not crash.