skip to Main Content

I have a react hook that detects a click outside the element:

export const useClickOutside = (
    ref: React.MutableRefObject<HTMLDivElement | null>,
    callback: Function,
) => {
    const handleClick = (e: MouseEvent) => {
        if (ref.current && !ref.current.contains(e.target as Node) {
            callback();
        }
    };

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

        return () => {
            document.removeEventListener('click', handleClick, true);
        };
    }, [ref, callback]);
};

I use it to close Dropdown elements. I thought that this event would be triggered in any situation, with any click anywhere in the document.

But in my application there are schemes that are based on mxGraph. They are built inside svg. And when clicking on the labels of some objects, the document click handler does not work.

For what reasons can this handler fail?

2

Answers


  1. The problem is you have added useEffect inside the function put it seperate outside the function here is the updated code.

    useEffect(() => {
            document.addEventListener('click', handleClick, true);
    
            return () => {
                document.removeEventListener('click', handleClick, true);
            };
        }, [ref, callback]);
    
    
    
    export const useClickOutside = (
        ref: React.MutableRefObject<HTMLDivElement | null>,
        callback: Function,
    ) => {
        const handleClick = (e: MouseEvent) => {
            if (ref.current && !ref.current.contains(e.target as Node) {
                callback();
            }
        };
    };
    
    Login or Signup to reply.
  2. To prevent the dependencies of the useEffect hook changing on every render, you should move the handleClick function inside the useEffect hook. Alternatively, wrap the definition of handleClick in its own useCallback hook.

    Here’s a final implementation that takes this into account:

    export const useClickOutside = (
      ref: React.MutableRefObject<HTMLDivElement | null>,
      callback: Function,
    ) => {
      React.useEffect(() => {
        const handleClick = (e: MouseEvent) => {
          if (ref.current && !ref.current.contains(e.target as Node)) {
            callback();
          }
        };
    
        document.addEventListener('click', handleClick, true);
    
        return () => {
          document.removeEventListener('click', handleClick, true);
        };
      }, [callback, ref]);
    };
    

    Or using the useCallback hook:

    export const useClickOutside = (
      ref: React.MutableRefObject<HTMLDivElement | null>,
      callback: Function,
    ) => {
      const handleClick = React.useCallback(
        (e: MouseEvent) => {
          if (ref.current && !ref.current.contains(e.target as Node)) {
            callback();
          }
        },
        [callback, ref],
      );
    
      React.useEffect(() => {
        document.addEventListener('click', handleClick, true);
    
        return () => {
          document.removeEventListener('click', handleClick, true);
        };
      }, [handleClick]);
    };
    

    As a side note your syntax is also incorrect. You are missing a closing bracket in the ref.current && !ref.current.contains(e.target as Node) expression. It should have been:

    const handleClick = (e: MouseEvent) => {
      if (ref.current && !ref.current.contains(e.target as Node)) {
        callback();
      } 
    };
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search