prerequisite: Please read my previous post to get an understanding of how one can implement a click outside listener in React in the first place, once you have an idea, this tutorial will become a lot simpler.
In the previous post, we learned how to implement a click outside listener without using any third-party libraries within just 10 lines of code.
In this tutorial, we will take a step further, and implement the same concept using our own custom hook, so let's begin.
Here's the hook:
// hooks/useClickOutsideListenerRef.tsx
import { useCallback, useEffect, useRef } from 'react'
export const useClickOutsideListenerRef = (onClose: () => void) => {
const ref = useRef(null)
const escapeListener = useCallback((e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose()
}
}, [])
const clickListener = useCallback(
(e: MouseEvent) => {
if (!(ref.current! as any).contains(e.target)) {
onClose?.()
}
},
[ref.current],
)
useEffect(() => {
document.addEventListener('click', clickListener)
document.addEventListener('keyup', escapeListener)
return () => {
document.removeEventListener('click', clickListener)
document.removeEventListener('keyup', escapeListener)
}
}, [])
return ref
}
Usage Example:
// components/Dialog.tsx
import React from 'react'
import { useClickOutsideListenerRef } from '../hooks/useClickoutsideListenerRef'
export interface IDialogProps {
onClose: () => void
}
export const Dialog: React.FC<IDialogProps> = props => {
const { onClose, children } = props
const ref = useClickOutsideListenerRef(onClose)
// I'm using tailwindcss for my css, you can use whatever you want.
return (
<div className='w-screen h-screen fixed inset-0 z-50 dialog-container'>
<div className='flex h-full'>
<div
ref={ref}
className='bg-white p-3 md:w-1/3 max-w-3/4 rounded overflow-auto'
>
{children}
</div>
</div>
<style jsx={true}>{`
.dialog-container {
background-color: rgba(0, 0, 0, 0.25);
}
`}</style>
</div>
)
}
Explanation
The useClickOutsideListenerRef hook takes a function which is invoked when the user clicks outside your component, and returns a ref which you need to refer to your Content Element, meaning the element that you want to interact with, i.e in this example the actual dialog box. So basically whenever the user click's outside the refered div element, the onClose method is invoked and the dialog is closed, but when the user click's inside the refered div the dialog remains open and stays interactive.
Enjoy.
Top comments (3)
This is by far the best post I've read so far. Thank you very much, it worked perfectly!
how do you use the "onClose" prop when you import your component somewhere?
well, you must track your component's state, either its a dialog box, a context menu or any other component, the onClose method is called when the user clicks outside of your reffered component, it's upto you wheather you want to change state or call an API or do something else.