Reputation: 402
I created a hook for detecting an outside click of a component:
export const useDetectOuterClick = () => {
const ref = useRef(null);
const [visible, setVisible] = useState(false);
const outerClickHandler = (event) => {
if (ref.current && !ref.current.contains(event.target)) {
setVisible(false);
}
}
useEffect(() => {
document.addEventListener('mousedown', (e) => outerClickHandler(e), true);
return () => {
document.addEventListener('mousedown', (e) => outerClickHandler(e), true);
}
}, [ref]);
return { visible, setVisible, ref };
}
I want to include in that hook several elements not just one, meaning detecting click outside of eather one of them.
I have tried to add another ref
:
export const useDetectOuterClick = () => {
const ref = useRef(null);
const ref2 = useRef(null);
const [visible, setVisible] = useState(false);
const outerClickHandler = (event) => {
if (ref.current && !ref.current.contains(event.target) && !ref2.current.contains(event.target)) {
setVisible(false);
}
}
useEffect(() => {
document.addEventListener('mousedown', (e) => outerClickHandler(e), true);
return () => {
document.addEventListener('mousedown', (e) => outerClickHandler(e), true);
}
}, [ref, ref2]);
return { visible, setVisible, ref, ref2 };
}
But it does not work! help please ?
Upvotes: 1
Views: 1960
Reputation: 111
The problem comes from injecting dependencies in useEffect.
When you set a new Ref, useEffect is called firstly for addEventListener and only after, the previous useEffect destuctor is called to removeEventListener. So, you don't have listener at end.
Every times your useEffect dependencies change, the useEffect destructor is called. And not in the order you think.
Edit
This is my function to useClicOutside. Just call two times the function for the two ref you want watch.
note : This code take into account the drag event, if you doesnt care, pass listenDrag to false by default
import { useEffect } from "react";
export function useOnClickOutside(ref, handler, listenDrag = true) {
useEffect(
() => {
let isDrag = false;
function moveListener(e) {
isDrag = true;
}
const listener = event => {
// Do nothing if clicking ref's element or descendent elements
if (!ref.current || ref.current.contains(event.target)) {
return;
}
if (!isDrag || !listenDrag) {
handler(event);
}
isDrag = false;
};
document.addEventListener("mousedown", () => (isDrag = false));
document.addEventListener("mousemove", moveListener);
document.addEventListener("mouseup", listener);
document.addEventListener("touchstart", () => (isDrag = false));
document.addEventListener("touchmove", moveListener);
document.addEventListener("touchend", listener);
return () => {
document.removeEventListener("mousedown", () => (isDrag = false));
document.removeEventListener("mousemove", moveListener);
document.removeEventListener("mouseup", listener);
document.removeEventListener("touchstart", () => (isDrag = false));
document.removeEventListener("touchmove", moveListener);
document.removeEventListener("touchend", listener);
};
},
// Add ref and handler to effect dependencies
// It's worth noting that because passed in handler is a new ...
// ... function on every render that will cause this effect ...
// ... callback/cleanup to run every render. It's not a big deal ...
// ... but to optimize you can wrap handler in useCallback before ...
// ... passing it into this hook.
[ref, handler]
);
}
Upvotes: 1