Skip to content

Instantly share code, notes, and snippets.

@dagstuan
Created June 17, 2021 09:45
Show Gist options
  • Save dagstuan/77ba9bbd9e6df55b3c9ba452f150a068 to your computer and use it in GitHub Desktop.
Save dagstuan/77ba9bbd9e6df55b3c9ba452f150a068 to your computer and use it in GitHub Desktop.
useOnClickOutside
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;
}
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