Skip to content

Instantly share code, notes, and snippets.

@oleggrishechkin
Last active December 17, 2023 19:13
Show Gist options
  • Save oleggrishechkin/a273f536d121192c46b4818b5fc7df36 to your computer and use it in GitHub Desktop.
Save oleggrishechkin/a273f536d121192c46b4818b5fc7df36 to your computer and use it in GitHub Desktop.
useOutsideClick hook to support nested portals
import { useRef, useEffect, useCallback, useLayoutEffect } from 'react';
const useIsomorphicLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;
export const useOutsideClick = (onOutsideClick: EventListener) => {
const clickedRef = useRef(false);
const onMouseDownOrTouchStart = useCallback(() => {
clickedRef.current = true;
}, []);
const outsideClickRef = useRef<EventListener | null>(null);
useIsomorphicLayoutEffect(() => {
outsideClickRef.current = onOutsideClick;
}, [onOutsideClick]);
useEffect(() => {
const onDocumentMouseDownOrTouchStart: EventListener = (event) => {
if (clickedRef.current || event.defaultPrevented || !outsideClickRef.current) {
clickedRef.current = false;
return;
}
setTimeout(() => {
if (!clickedRef.current && outsideClickRef.current) {
outsideClickRef.current(event);
}
}, 0);
};
window.addEventListener('mousedown', onDocumentMouseDownOrTouchStart);
window.addEventListener('touchstart', onDocumentMouseDownOrTouchStart);
return () => {
window.removeEventListener('mousedown', onDocumentMouseDownOrTouchStart);
window.removeEventListener('touchstart', onDocumentMouseDownOrTouchStart);
};
}, []);
return onMouseDownOrTouchStart;
};
@oleggrishechkin
Copy link
Author

oleggrishechkin commented Dec 17, 2023

This is a smart useOutsideClick hook to support nested portals. Events in React bubble throught portal and you can catch event on parent even child node was portaled somewhere to the root (you can't do this with real DOM event). So, because child portaled node not a child in DOM, you can't use contains() method to recognize nesting and outside click. But you can use bubbling!

Logic:

  • if we can catch mousedonw/touchstart dom event in mousedown/touchstart react event handler on target node than this is not a click outside
  • if mousedown/touchstart react event handler on target node wasn't called on mousedown/touchstart dom event somewhere than this is a click outside

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment