You can use useEventListener on the document to detect clicks outside of a specific element (like a modal) by checking if the click target is contained within the element's ref.
import * as React from "react";
import { useEventListener } from "@uidotdev/usehooks";
export default function App() {
const ref = React.useRef(null);
const [isOpen, setIsOpen] = React.useState(false);
const handleClick = (e) => {
const element = ref.current;
// If the modal is open and the click was NOT inside the modal element
if (element && !element.contains(e.target)) {
setIsOpen(false);
}
};
// Attach listener to the entire document
useEventListener(document, "mousedown", handleClick);
return (
<section>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
{isOpen && (
<dialog ref={ref}>
<h2>Modal</h2>
<p>Click outside to close.</p>
<button onClick={() => setIsOpen(false)}>Close</button>
</dialog>
)}
</section>
);
}
import * as React from "react";
import { useEventListener } from "@uidotdev/usehooks";
import { closeIcon } from "./icons";
export default function App() {
const ref = React.useRef(null);
const [isOpen, setIsOpen] = React.useState(false);
const handleClick = (e) => {
const element = ref.current;
if (element && !element.contains(e.target)) {
setIsOpen(false);
}
};
useEventListener(document, "mousedown", handleClick);
return (
<section>
<h1>useEventListener</h1>
<div style={{ minHeight: "200vh" }}>
<button className="link" onClick={() => setIsOpen(true)}>
Click me
</button>
</div>
{isOpen && (
<dialog ref={ref}>
<button onClick={() => setIsOpen(false)}>{closeIcon}</button>
<h2>Modal</h2>
<p>
Click outside the modal to close (or use the button) whatever you
prefer.
</p>
</dialog>
)}
</section>
);
}