A modern, StrictMode-safe React library for detecting clicks outside elements. Provides both a React hook (useClickOutside) and a Higher-Order Component (withClickOutside) for maximum flexibility.
- ✅ React 18+ compatible with full StrictMode support
- ✅ TypeScript native with full type definitions
- ✅ Zero dependencies (except React peer dependency)
- ✅ Tiny bundle size - ~600 bytes gzipped, tree-shakeable
- ✅ No
findDOMNode- uses modern React patterns - ✅ Both hook and HOC - works with functional and class components
- ✅ Touch-friendly - handles both mouse and touch events
npm install react-safe-click-outsideor
yarn add react-safe-click-outsideor
pnpm add react-safe-click-outsidePerfect for functional components:
import { useRef } from 'react';
import { useClickOutside } from 'react-safe-click-outside';
function Dropdown() {
const dropdownRef = useRef<HTMLDivElement>(null);
useClickOutside(dropdownRef, () => {
console.log('Clicked outside dropdown!');
// Close dropdown, hide modal, etc.
});
return (
<div ref={dropdownRef}>
<h3>Dropdown Menu</h3>
<ul>
<li>Option 1</li>
<li>Option 2</li>
<li>Option 3</li>
</ul>
</div>
);
}import { useRef, useState } from 'react';
import { useClickOutside } from 'react-safe-click-outside';
function Modal() {
const [isOpen, setIsOpen] = useState(false);
const modalRef = useRef<HTMLDivElement>(null);
useClickOutside(modalRef, () => {
if (isOpen) {
setIsOpen(false);
}
});
return (
<div>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
{isOpen && (
<div
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'rgba(0,0,0,0.5)',
}}
>
<div
ref={modalRef}
style={{
background: 'white',
padding: '2rem',
margin: '100px auto',
maxWidth: '500px',
}}
>
<h2>Modal Content</h2>
<p>Click outside to close!</p>
</div>
</div>
)}
</div>
);
}Perfect for class components or legacy React code:
import React, { Component } from 'react';
import {
withClickOutside,
WithClickOutsideProps,
} from 'react-safe-click-outside';
interface Props extends WithClickOutsideProps {
title: string;
}
class Popover extends Component<Props> {
handleClickOutside = () => {
console.log('Clicked outside popover!');
// Handle outside click
};
render() {
return (
<div>
<h3>{this.props.title}</h3>
<p>Popover content</p>
</div>
);
}
}
// Wrap the component
export default withClickOutside(Popover);
// Usage:
// <Popover title="My Popover" onClickOutside={handleClose} />import {
withClickOutside,
WithClickOutsideProps,
} from 'react-safe-click-outside';
interface Props extends WithClickOutsideProps {
message: string;
}
const Tooltip: React.FC<Props> = ({ message }) => {
return <div className='tooltip'>{message}</div>;
};
export default withClickOutside(Tooltip);A React hook that triggers a callback when clicking outside a referenced element.
Parameters:
ref: RefObject<HTMLElement>- A React ref object pointing to the element to monitorhandler: (event: MouseEvent | TouchEvent) => void- Callback function executed when clicking outside
Returns: void
Example:
const ref = useRef<HTMLDivElement>(null);
useClickOutside(ref, (event) => {
console.log('Clicked outside!', event);
});A Higher-Order Component that adds click-outside detection to any component.
Parameters:
Component: ComponentType<P>- The component to wrap
Returns: ComponentType<P & WithClickOutsideProps>
Injected Props:
onClickOutside?: (event: MouseEvent | TouchEvent) => void- Callback for outside clicks
Example:
const EnhancedComponent = withClickOutside(MyComponent);
<EnhancedComponent onClickOutside={() => console.log('Outside click!')} />;This library is written in TypeScript and includes full type definitions. No need to install @types/* packages.
import {
useClickOutside,
withClickOutside,
WithClickOutsideProps,
} from 'react-safe-click-outside';Works in all modern browsers that support:
- ES6+
- React 17+
- Standard DOM events (mousedown, touchstart)
The library listens for mousedown and touchstart events on the document. When an event occurs, it checks if the click target is outside the referenced element. If so, it triggers the provided callback.
Key Implementation Details:
- Uses modern React patterns (no
findDOMNode) - StrictMode-safe
- Properly cleans up event listeners on unmount
- Uses
display: contentsin HOC to avoid wrapper div affecting layout - Handles both mouse and touch events
- Memoize your handler to avoid unnecessary re-renders:
const handleClickOutside = useCallback(() => {
setIsOpen(false);
}, []);
useClickOutside(ref, handleClickOutside);- Conditional logic should be inside the handler:
useClickOutside(ref, () => {
if (isOpen) {
setIsOpen(false);
}
});- Multiple refs: If you need to ignore clicks on multiple elements, use a wrapper:
<div ref={wrapperRef}>
<button>Trigger</button>
<div>Content</div>
</div>- Dropdown menus
- Modals and dialogs
- Popovers and tooltips
- Context menus
- Date pickers
- Autocomplete suggestions
- Mobile navigation menus
Contributions are welcome! Please feel free to submit a Pull Request.
MIT
Built with ❤️ for the React community