Last active
December 17, 2023 19:13
-
-
Save oleggrishechkin/a273f536d121192c46b4818b5fc7df36 to your computer and use it in GitHub Desktop.
useOutsideClick hook to support nested portals
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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: