2

I have a custom hook that handles clicks outside of a component:

const useOutsideClick = (ref, callback) => {
  const handleClick = e => {

    if (ref.current && !ref.current.contains(e.target)) {
      callback();
    }
  };

  useEffect(() => {
    document.addEventListener("click", handleClick);

    return () => {
      document.removeEventListener("click", handleClick);
    };
  });
};

I have used this hook in a component like this:

const MyComponent = () => {
  const container = useRef();
  const [isOpen, setIsOpen] = useState(false);

  useOutsideClick(container, () => {
    console.log("clicked outside");
  });
  return (
    <>
      <span>another element</span>

      <button onClick={() => setIsOpen(false)}>click</button>
      <div ref={container}></div>
    </>
  );
};

The problem is when I click on the span, everything works fine. But, when I click on the button an it updates the state, it doesn't enter useOutsideClick callback function. How can I solve this problem?

2 Answers 2

2

The issue here is that useEffect has no dependency array, so it is being called on every render. Also, it is better to keep the definition of handleClick inside the hook as it is only going to be used in the first render; otherwise if would be defined on every render.

const useOutsideClick = (ref, callback) => {
    useEffect(() => {
        const handleClick = e => {
            if (ref.current && !ref.current.contains(e.target)) {
                callback();
            }
        };

        document.addEventListener("click", handleClick);

        return () => {
            document.removeEventListener("click", handleClick);
        };
    }, []);
};
Sign up to request clarification or add additional context in comments.

1 Comment

You right! I copied this code from medium and I didn't see this problem!
0

I found that the best solution for useOutsideClick hook is this:

function useOutsideClick(ref, handler) {
  useEffect(
    () => {
      const listener = event => {
        if (!ref.current || ref.current.contains(event.target)) {
          return;
        }

        handler(event);
      };

      document.addEventListener("mousedown", listener);
      document.addEventListener("touchstart", listener);

      return () => {
        document.removeEventListener("mousedown", listener);
        document.removeEventListener("touchstart", listener);
      };
    },

    [ref, handler]
  );
}

Comments

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.