Code Snippets
/

React useClickOutside Hook

React useClickOutside Hook

Dropdowns, popovers, and modals all need to dismiss when the user clicks outside their wrapper. The useClickOutside hook centralises the listener wiring so feature components stay declarative. This snippet covers the pointerdown-based version, an ignore-list variant for portal triggers, and the keyboard escape sibling that completes the dismissal pattern.

JavaScript
Medium
3 snippets
react
hooks
code-template
js-event-loop

270 views

6

function useClickOutside(ref, handler) {
    const handlerRef = useRef(handler);
    useEffect(() => { handlerRef.current = handler; }, [handler]);
    useEffect(() => {
        if (typeof document === 'undefined') return undefined;
        const onPointerDown = (e) => {
            const node = ref && ref.current;
            if (!node || node.contains(e.target)) return;
            handlerRef.current(e);
        };
        document.addEventListener('pointerdown', onPointerDown, true);
        return () => document.removeEventListener('pointerdown', onPointerDown, true);
    }, [ref]);
}

function useRef(v) { return { current: v }; }
function useEffect(fn) { fn(); }
const popoverRef = { current: { contains: () => false } };
useClickOutside(popoverRef, () => console.log('dismiss'));
console.log('outside listener attached');

Listening on pointerdown (capture phase) catches the press before any click handler inside the popover runs, which avoids race conditions with stopPropagation. The hook bails out early if the target lives inside ref.current, which uses the DOM Node.contains check. Capture phase plus pointerdown also handles touch correctly without a separate listener. This is the right shape for popovers, autocomplete dropdowns, and any UI that should dismiss on a click anywhere else.