DEV Community

vibhanshu pandey
vibhanshu pandey

Posted on

Click outside listener for React components in 10 lines of code [Using Hooks]

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
}
Enter fullscreen mode Exit fullscreen mode

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
gabrielmendes98 profile image
Gabriel Santiago

This is by far the best post I've read so far. Thank you very much, it worked perfectly!

Collapse
 
pogadev18 profile image
Pogacean Bogdan • Edited

how do you use the "onClose" prop when you import your component somewhere?

Collapse
 
vibhanshu909 profile image
vibhanshu pandey

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.