useClickOutsideLoading saved progress…

useClickOutside

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.

Signature

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.

Examples

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

Notes

  • "Inside" includes descendants. A click on a button or list item nested within the protected element counts as inside. Use Node.contains — not an identity check against the element — so nested clicks are recognized.
  • Listen on 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.
  • The handler must never go stale. If the component re-renders with a new handler (inline arrows are recreated every render), the next outside click must call the new one — without tearing down and re-attaching the document listener.
  • Clean up on unmount. Remove the document listener when the component unmounts, or it leaks and keeps firing into a component that no longer exists.
  • An unattached ref is safe. If the ref was never placed on an element, ref.current is null — calling the handler should be skipped, not crash.
Loading editor…