A React hook for detecting clicks outside of specified elements. Commonly used in components like dialogs, popovers, modals, and dropdowns to implement automatic closing behavior when users click elsewhere on the page.
npm install @chakra-ui/react-use-outside-clickimport { useOutsideClick, type UseOutsideClickProps } from "@chakra-ui/react-use-outside-click";For CommonJS:
const { useOutsideClick } = require("@chakra-ui/react-use-outside-click");Dependencies:
// Internal dependency (automatically included)
import { useCallbackRef } from "@chakra-ui/react-use-callback-ref";import React, { useRef, useState } from "react";
import { useOutsideClick } from "@chakra-ui/react-use-outside-click";
function Modal() {
const [isOpen, setIsOpen] = useState(true);
const modalRef = useRef<HTMLDivElement>(null);
useOutsideClick({
ref: modalRef,
handler: () => setIsOpen(false),
});
if (!isOpen) return null;
return (
<div className="overlay">
<div ref={modalRef} className="modal">
<h2>Modal Content</h2>
<p>Click outside to close this modal</p>
</div>
</div>
);
}Detects clicks occurring outside specified DOM elements and executes a callback handler. Uses a sophisticated pointer event strategy that handles mouse, touch, and pointer events with proper state tracking and safeguards against emulated mouse events on touch devices.
/**
* React hook for detecting clicks outside specified elements
* @param props - Configuration object for the hook
*/
function useOutsideClick(props: UseOutsideClickProps): void;
interface UseOutsideClickProps {
/** Whether the hook is enabled (defaults to true) */
enabled?: boolean;
/** Reference to a DOM element to detect outside clicks for */
ref: React.RefObject<HTMLElement>;
/** Function invoked when a click is triggered outside the referenced element */
handler?: (e: Event) => void;
}Key Features:
mousedown, mouseup, touchstart, and touchend eventsignoreEmulatedMouseEvents flagenabled propUsage Examples:
import React, { useRef, useState } from "react";
import { useOutsideClick } from "@chakra-ui/react-use-outside-click";
// Basic popover with outside click to close
function Popover({ children, content }: { children: React.ReactNode; content: string }) {
const [isVisible, setIsVisible] = useState(false);
const popoverRef = useRef<HTMLDivElement>(null);
useOutsideClick({
ref: popoverRef,
handler: () => setIsVisible(false),
enabled: isVisible, // Only listen when popover is visible
});
return (
<div className="popover-container">
<button onClick={() => setIsVisible(!isVisible)}>
{children}
</button>
{isVisible && (
<div ref={popoverRef} className="popover">
{content}
</div>
)}
</div>
);
}
// Dropdown menu with outside click handling
function DropdownMenu() {
const [isOpen, setIsOpen] = useState(false);
const menuRef = useRef<HTMLUListElement>(null);
useOutsideClick({
ref: menuRef,
handler: (event) => {
console.log("Clicked outside at:", event.target);
setIsOpen(false);
},
});
return (
<div className="dropdown">
<button onClick={() => setIsOpen(!isOpen)}>
Menu {isOpen ? "▲" : "▼"}
</button>
{isOpen && (
<ul ref={menuRef} className="menu">
<li>Option 1</li>
<li>Option 2</li>
<li>Option 3</li>
</ul>
)}
</div>
);
}
// Conditional hook usage
function ConditionalOutsideClick({ shouldListen }: { shouldListen: boolean }) {
const ref = useRef<HTMLDivElement>(null);
useOutsideClick({
ref,
handler: () => console.log("Outside click detected"),
enabled: shouldListen, // Dynamically enable/disable
});
return (
<div ref={ref}>
Content that {shouldListen ? "listens for" : "ignores"} outside clicks
</div>
);
}
// Advanced usage with event details
function AdvancedOutsideClick() {
const [isOpen, setIsOpen] = useState(false);
const ref = useRef<HTMLDivElement>(null);
useOutsideClick({
ref,
handler: (event) => {
// Access event details for advanced handling
console.log("Event type:", event.type);
console.log("Event target:", event.target);
console.log("Timestamp:", event.timeStamp);
setIsOpen(false);
},
enabled: isOpen, // Only listen when open
});
return (
<div>
<button onClick={() => setIsOpen(true)}>Open Panel</button>
{isOpen && (
<div ref={ref} style={{ border: "1px solid black", padding: "20px" }}>
Click outside to close
</div>
)}
</div>
);
}Implementation Details:
The hook uses sophisticated internal logic:
isPointerDown flag to track pointer interaction stateignoreEmulatedMouseEvents flag on touch end to prevent duplicate eventsisValidEvent helper to ensure events occur on elements within the documentError Handling:
The hook gracefully handles edge cases:
isValidEvent)ignoreEmulatedMouseEvents)Internal Helper Functions:
/**
* Validates if an event should trigger the outside click handler
* @param event - The DOM event to validate
* @param ref - React ref to the element being monitored
* @returns boolean indicating if the event is valid for outside click
*/
function isValidEvent(event: Event, ref: React.RefObject<HTMLElement>): boolean;
/**
* Gets the owner document for a given DOM element
* @param node - The DOM element to get the document for
* @returns The owner document or global document as fallback
*/
function getOwnerDocument(node?: Element | null): Document;Event Handling Sequence:
mousedown/touchstart events set isPointerDown = true if outside targetmouseup/touchend events trigger handler if isPointerDown is true and event is still outsidetouchend sets ignoreEmulatedMouseEvents = true to prevent duplicate mouseup handlingisValidEvent to ensure proper document containmentPlatform Compatibility: