React: Detect a click outside an element
Today, I had to close a modal, after clicking outside of it. Here's how I've done it:
export default function App() {
const [open, setOpen] = React.useState(false)
const modalRef = React.useRef<HTMLDivElement>(null)
React.useEffect(() => {
const onOutsideClick = (event: MouseEvent) => {
const modalEl = modalRef.current
// Do nothing if clicking ref's element or its descendent elements
if (!modalEl || modalEl.contains(event.target as Node)) {
return
}
setOpen(false)
}
window.addEventListener('mousedown', onOutsideClick)
return () => {
window.removeEventListener('mousedown', onOutsideClick)
}
}, [setOpen])
return (
<>
<button onClick={() => setOpen(true)}>Open modal</button>
{open && (
<div className="overlay">
<div className="modal" ref={modalRef}>
<h1>Modal content</h1>
</div>
</div>
)}
</>
)
}
You could go one step further and extract it to a custom hook, like so:
function useOnClickOutside<T extends HTMLElement = HTMLElement>(
callback: () => void,
) {
const ref = React.useRef<T>(null)
React.useEffect(() => {
const onOutsideClick = (event: MouseEvent) => {
// Do nothing if clicking ref's element or its descendent elements
if (!ref.current || ref.current.contains(event.target as Node)) {
return
}
callback()
}
window.addEventListener('mousedown', onOutsideClick)
return () => {
window.removeEventListener('mousedown', onOutsideClick)
}
}, [callback])
return ref
}
export default function App() {
const [open, setOpen] = React.useState(false)
const modalRef = useOnClickOutside<HTMLDivElement>(() => setOpen(false))
return (
<>
<button onClick={() => setOpen(true)}>Open modal</button>
{open && (
<div className="overlay">
<div className="modal" ref={modalRef}>
<h1>Modal content</h1>
</div>
</div>
)}
</>
)
}
My example is very simple, for something more advanced, check usehooks-ts in the resources.