Created
June 17, 2021 09:45
-
-
Save dagstuan/77ba9bbd9e6df55b3c9ba452f150a068 to your computer and use it in GitHub Desktop.
useOnClickOutside
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 { useEffect, useState, useCallback } from 'react'; | |
import { useLatest } from './useLatest'; | |
export function useClickOutsideComponent<T extends HTMLElement>( | |
callback: (event?: MouseEvent | TouchEvent) => void, | |
) { | |
const latestCallback = useLatest(callback); | |
const [domNode, setDomNode] = useState<T>(); | |
const ref = useCallback((node: T | null) => { | |
setDomNode(node || undefined); | |
}, []); | |
useEffect(() => { | |
let ignoreClick = false; | |
const mouseUpListener = (event: MouseEvent) => { | |
if (domNode && !domNode.contains(event.target as Node)) { | |
// If the mouseup happened outside the dom node, we should | |
// ignore the click event that follows since the mousedown | |
// started inside the component | |
ignoreClick = true; | |
} | |
document.removeEventListener('mouseup', mouseUpListener); | |
document.removeEventListener('dragend', dragEndListener); | |
}; | |
const dragEndListener = () => { | |
// We need to remove the mouseup-listener in case the user dragged something | |
// so we dont register ignore click on the next cycle | |
document.removeEventListener('mouseup', mouseUpListener); | |
document.removeEventListener('dragend', dragEndListener); | |
}; | |
const mouseDownListener = () => { | |
// Listen for mouseup and dragend, and check if they are outside the dom node. | |
document.addEventListener('mouseup', mouseUpListener); | |
document.addEventListener('dragend', dragEndListener); | |
}; | |
const clickListener = (event: MouseEvent | TouchEvent) => { | |
if (ignoreClick) { | |
ignoreClick = false; | |
return; | |
} | |
if ( | |
!latestCallback.current || | |
!domNode || | |
domNode.contains(event.target as Node) | |
) { | |
return; | |
} | |
latestCallback.current(event); | |
}; | |
if (domNode) { | |
domNode.addEventListener('mousedown', mouseDownListener); | |
} | |
document.addEventListener('click', clickListener); | |
document.addEventListener('touchstart', clickListener); | |
return () => { | |
document.removeEventListener('click', clickListener); | |
document.removeEventListener('touchstart', clickListener); | |
if (domNode) { | |
domNode.removeEventListener('mousedown', mouseDownListener); | |
} | |
document.removeEventListener('mouseup', mouseUpListener); | |
document.removeEventListener('dragend', dragEndListener); | |
}; | |
}, [domNode, latestCallback]); | |
return ref; | |
} |
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 } from 'react'; | |
export const useLatest = <T>(value: T) => { | |
const ref = useRef(value); | |
useEffect(() => { | |
ref.current = value; | |
}); | |
return ref; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment