Skip to main content

useOutsideClick

A custom React hook that detects clicks outside of a specified element. Useful for implementing dropdowns, modals, popovers, and other UI components that should close when the user clicks outside of them.

Hook Signature

const useOutsideClick = (
  ref: React.RefObject<HTMLDivElement>,
  callback: Function
) => void

Parameters

ref
React.RefObject<HTMLDivElement>
required
A React ref object attached to the element you want to monitor. The hook will detect clicks outside of this element.Created using useRef<HTMLDivElement>(null).
callback
Function
required
A function to execute when a click outside the referenced element is detected. The callback receives the event object as an argument.Type: (event: MouseEvent | TouchEvent) => void

Return Value

This hook does not return any value. It sets up event listeners that trigger the callback when appropriate.

Usage Examples

Basic Modal Example

import React, { useRef, useState } from 'react';
import { useOutsideClick } from '@/app/hooks/useOutsideClick';

const Modal: React.FC = () => {
  const [isOpen, setIsOpen] = useState(false);
  const modalRef = useRef<HTMLDivElement>(null);

  // Close modal when clicking outside
  useOutsideClick(modalRef, () => {
    setIsOpen(false);
  });

  if (!isOpen) return null;

  return (
    <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center">
      <div ref={modalRef} className="bg-white p-6 rounded-lg shadow-xl">
        <h2>Modal Content</h2>
        <p>Click outside to close</p>
      </div>
    </div>
  );
};
import React, { useRef, useState } from 'react';
import { useOutsideClick } from '@/app/hooks/useOutsideClick';

const Dropdown: React.FC = () => {
  const [isOpen, setIsOpen] = useState(false);
  const dropdownRef = useRef<HTMLDivElement>(null);

  useOutsideClick(dropdownRef, () => {
    if (isOpen) {
      setIsOpen(false);
    }
  });

  return (
    <div ref={dropdownRef} className="relative">
      <button 
        onClick={() => setIsOpen(!isOpen)}
        className="px-4 py-2 bg-blue-500 text-white rounded"
      >
        Toggle Dropdown
      </button>
      
      {isOpen && (
        <div className="absolute mt-2 w-48 bg-white rounded shadow-lg">
          <a href="#" className="block px-4 py-2 hover:bg-gray-100">Option 1</a>
          <a href="#" className="block px-4 py-2 hover:bg-gray-100">Option 2</a>
          <a href="#" className="block px-4 py-2 hover:bg-gray-100">Option 3</a>
        </div>
      )}
    </div>
  );
};

Popover Example

import React, { useRef, useState } from 'react';
import { useOutsideClick } from '@/app/hooks/useOutsideClick';

const Popover: React.FC = () => {
  const [isVisible, setIsVisible] = useState(false);
  const popoverRef = useRef<HTMLDivElement>(null);

  useOutsideClick(popoverRef, (event) => {
    console.log('Clicked outside:', event.target);
    setIsVisible(false);
  });

  return (
    <div className="relative inline-block">
      <button 
        onClick={() => setIsVisible(true)}
        className="px-4 py-2 bg-green-500 text-white rounded"
      >
        Show Info
      </button>
      
      {isVisible && (
        <div 
          ref={popoverRef}
          className="absolute z-10 mt-2 p-4 bg-white border rounded shadow-lg"
        >
          <p className="text-sm">This is a popover with additional information.</p>
        </div>
      )}
    </div>
  );
};

Advanced: Multiple Refs

import React, { useRef, useState } from 'react';
import { useOutsideClick } from '@/app/hooks/useOutsideClick';

const ComplexComponent: React.FC = () => {
  const [activePanel, setActivePanel] = useState<string | null>(null);
  const panel1Ref = useRef<HTMLDivElement>(null);
  const panel2Ref = useRef<HTMLDivElement>(null);

  // Handle panel 1
  useOutsideClick(panel1Ref, () => {
    if (activePanel === 'panel1') {
      setActivePanel(null);
    }
  });

  // Handle panel 2
  useOutsideClick(panel2Ref, () => {
    if (activePanel === 'panel2') {
      setActivePanel(null);
    }
  });

  return (
    <div className="flex gap-4">
      <div ref={panel1Ref}>
        <button onClick={() => setActivePanel('panel1')}>Open Panel 1</button>
        {activePanel === 'panel1' && <div>Panel 1 Content</div>}
      </div>
      
      <div ref={panel2Ref}>
        <button onClick={() => setActivePanel('panel2')}>Open Panel 2</button>
        {activePanel === 'panel2' && <div>Panel 2 Content</div>}
      </div>
    </div>
  );
};

Implementation Details

import React, { useEffect } from "react";

export const useOutsideClick = (
  ref: React.RefObject<HTMLDivElement>,
  callback: Function
) => {
  useEffect(() => {
    const listener = (event: any) => {
      // Do nothing if clicking ref's element or descendent elements
      if (!ref.current || ref.current.contains(event.target)) {
        return;
      }
      callback(event);
    };

    document.addEventListener("mousedown", listener);
    document.addEventListener("touchstart", listener);

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

How It Works

Important Notes

Common Use Cases

  • Modal dialogs that close on backdrop click
  • Dropdown menus and select components
  • Context menus and right-click menus
  • Popovers and tooltips
  • Date pickers and time selectors
  • Autocomplete suggestion lists
  • Custom select dropdowns
  • Mobile navigation menus
  • Color pickers and other picker components

Build docs developers (and LLMs) love