1

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 ?

7
  • 1
    A side note, you probably want to remove the listener on component's unmount, not adding another listener. Commented Jul 12, 2020 at 14:28
  • @MoshFeu ok, can you instruct me how ? Commented Jul 12, 2020 at 14:31
  • 1
    Just like this one: stackoverflow.com/a/55360806/863110 Commented Jul 12, 2020 at 14:34
  • @MoshFeu ok thanks, do you have a suggestion about my problem ? Commented Jul 12, 2020 at 14:38
  • 1
    Your approach should work if you actually assign those 2 refs: codesandbox.io/s/magical-sun-wsoj8?file=/src/App.js Commented Jul 12, 2020 at 14:50

1 Answer 1

1

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]
  );
}
Sign up to request clarification or add additional context in comments.

2 Comments

Think different :) Why not pass ref as argument to your Hook ? And by this way, no more problem with change of dependencies
Otherwise, duplicate your useEffect, one by ref can fix your problem.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.