Skip to content

useOutsideClickEffect internal implementation question #270

@wo-o29

Description

@wo-o29

Describe the issue

A clear and concise description of what the issue is.

In the current useOutsideClickEffect hook usage, you create a state (wrapperEl), connect the setter function to the container, and pass the state as an argument to the hook, as shown below:

function Example() {
  const [wrapperEl, setWrapperEl] = useState<HTMLDivElement | null>(null);
  
  useOutsideClickEffect(wrapperEl, () => {
    console.log('Clicked outside!');
  });
  
  return <div ref={setWrapperEl}>Content</div>;
}

I find this approach somewhat scattered and inconvenient to use because related codes are spread out. Therefore, I created a hook that returns a callback ref function, which you simply connect to elements, making usage simpler:

const useOutsideClick = (callback: () => void) => {
const elementSetRef = useRef<Set<HTMLElement>>(new Set());

  useEffect(() => {
    const handleDocumentClick = ({ target }: MouseEvent) => {
      const elements = Array.from(elementSetRef.current);

      const isOutsideClick = elements.every(
        (element) => !element.contains(target as Node),
      );

      if (isOutsideClick && elements.length > 0) {
        callback();
      }
    };

    document.addEventListener("mousedown", handleDocumentClick);

    return () => {
      document.removeEventListener("mousedown", handleDocumentClick);
    };
  }, [callback]);

  return (element: HTMLElement | null) => {
    if (!element) {
      return;
    }

    elementSetRef.current.add(element);

    return () => {
      elementSetRef.current.delete(element);
    };
  };
};

// Example of use
function App() {
  const [isOpen, setIsOpen] = useState(true);
  const addToSafeZone = useOutsideClick(() => setIsOpen(false));

  return (
    <div>
      <div>{String(isOpen)}</div>
      <div ref={addToSafeZone}>A</div>
      <div ref={addToSafeZone}>B</div>
      <div>content</div>
    </div>
  );
}

I think this approach has the advantage of being simpler to use without requiring the user to create additional state manually.

Is there any perspective that I might be missing? If my approach is indeed better, I would like to improve the useOutsideClickEffect hook accordingly. I am curious about the maintainers’ opinions on this. :)

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions