@aniruddha1806/use-event-listener
v1.0.3
Published
A flexible, type-safe React hook for managing event listeners with automatic cleanup
Maintainers
Readme
React useEventListener Hook
A type-safe, versatile event listener hook for React applications with TypeScript support. Easily attach event listeners to any DOM element, window, or document with proper cleanup and type safety.
Installation
npm install @aniruddha1806/use-event-listenerFeatures
- 🎯 TypeScript support with full type definitions for all event types
- 🌐 Works with Window, Document, and any HTML Element
- 🔄 Supports both direct elements and React refs
- 🧹 Automatic cleanup on unmount to prevent memory leaks
- ⚠️ Development mode warnings for common mistakes
- 🛡️ Type-safe event handling with proper event types
- 📦 Tiny footprint with zero dependencies
- 🔍 Preserves the original listener reference between renders
Quick Start
import { useRef } from 'react';
import useEventListener from '@aniruddha1806/use-event-listener';
function App() {
// Listen for window events
useEventListener(window, 'resize', () => {
console.log('Window resized!');
});
// Listen for document events
useEventListener(document, 'visibilitychange', () => {
console.log('Tab visibility changed!');
});
// Listen for element events using a ref
const buttonRef = useRef(null);
useEventListener(buttonRef, 'click', () => {
console.log('Button clicked!');
});
return (
<div>
<h1>useEventListener Example</h1>
<button ref={buttonRef}>Click me</button>
</div>
);
}API
Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| target | Window \| Document \| HTMLElement \| RefObject<HTMLElement> \| Element \| RefObject<Element> | The event target |
| type | string | The event type (e.g., 'click', 'resize') |
| listener | (event: Event) => void | The event callback function |
| options | boolean \| AddEventListenerOptions | Optional event listener options |
Returns
void - This hook doesn't return anything.
Examples
Window Event Listeners
Listen for global window events:
import useEventListener from '@aniruddha1806/use-event-listener';
function WindowEvents() {
// Track window size
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
});
// Update dimensions on window resize
useEventListener(window, 'resize', () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
});
// Track online status
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEventListener(window, 'online', () => setIsOnline(true));
useEventListener(window, 'offline', () => setIsOnline(false));
return (
<div>
<h2>Window Size</h2>
<p>Width: {windowSize.width}px</p>
<p>Height: {windowSize.height}px</p>
<h2>Network Status</h2>
<p>You are {isOnline ? 'online' : 'offline'}</p>
</div>
);
}Document Event Listeners
Listen for document-level events:
import { useState } from 'react';
import useEventListener from '@aniruddha1806/use-event-listener';
function KeyboardShortcuts() {
const [lastKey, setLastKey] = useState('');
const [isFullScreen, setIsFullScreen] = useState(false);
// Track keyboard shortcuts
useEventListener(document, 'keydown', (event) => {
setLastKey(event.key);
// Handle keyboard shortcuts
if (event.key === 'F' && event.ctrlKey) {
event.preventDefault();
toggleFullScreen();
}
});
// Track document visibility
useEventListener(document, 'visibilitychange', () => {
console.log('Document visibility:', document.visibilityState);
});
const toggleFullScreen = () => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
setIsFullScreen(true);
} else {
document.exitFullscreen();
setIsFullScreen(false);
}
};
return (
<div>
<h2>Keyboard Shortcuts</h2>
<p>Last key pressed: {lastKey || 'None'}</p>
<p>Press Ctrl+F to toggle fullscreen</p>
<p>Fullscreen: {isFullScreen ? 'Yes' : 'No'}</p>
</div>
);
}Element Event Listeners with Refs
Listen for events on specific DOM elements using refs:
import { useRef, useState } from 'react';
import useEventListener from '@aniruddha1806/use-event-listener';
function DragAndDrop() {
const [position, setPosition] = useState({ x: 0, y: 0 });
const [isDragging, setIsDragging] = useState(false);
const elementRef = useRef(null);
// Handle mouse down on the element
useEventListener(elementRef, 'mousedown', (event) => {
setIsDragging(true);
// Prevent text selection during drag
event.preventDefault();
});
// Handle mouse move for dragging
useEventListener(document, 'mousemove', (event) => {
if (isDragging) {
setPosition({
x: event.clientX - 50, // Adjust for element size
y: event.clientY - 50,
});
}
});
// Handle mouse up to stop dragging
useEventListener(document, 'mouseup', () => {
setIsDragging(false);
});
return (
<div>
<h2>Drag and Drop</h2>
<div
ref={elementRef}
style={{
position: 'absolute',
left: `${position.x}px`,
top: `${position.y}px`,
width: '100px',
height: '100px',
backgroundColor: isDragging ? 'lightcoral' : 'lightblue',
cursor: 'grab',
userSelect: 'none',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '8px',
}}
>
Drag me
</div>
</div>
);
}Form Input Events
Handle form input events:
import { useRef, useState } from 'react';
import useEventListener from '@aniruddha1806/use-event-listener';
function FormEvents() {
const [inputValue, setInputValue] = useState('');
const [isFocused, setIsFocused] = useState(false);
const inputRef = useRef(null);
// Handle input changes
useEventListener(inputRef, 'input', (event) => {
setInputValue(event.target.value);
});
// Handle focus events
useEventListener(inputRef, 'focus', () => {
setIsFocused(true);
});
// Handle blur events
useEventListener(inputRef, 'blur', () => {
setIsFocused(false);
});
return (
<div>
<h2>Form Input Events</h2>
<input
ref={inputRef}
type="text"
placeholder="Type something..."
style={{
padding: '8px',
border: `2px solid ${isFocused ? 'blue' : 'gray'}`,
borderRadius: '4px',
outline: 'none',
}}
/>
<p>Current value: {inputValue}</p>
<p>Input is {isFocused ? 'focused' : 'not focused'}</p>
</div>
);
}Passive Event Listeners
Use passive event listeners for better scroll performance:
import { useRef } from 'react';
import useEventListener from '@aniruddha1806/use-event-listener';
function SmoothScroll() {
const scrollContainerRef = useRef(null);
// Use passive: true for better scroll performance
useEventListener(
scrollContainerRef,
'scroll',
(event) => {
console.log('Scroll position:', event.target.scrollTop);
},
{ passive: true }
);
return (
<div
ref={scrollContainerRef}
style={{
height: '300px',
overflow: 'auto',
border: '1px solid #ccc',
padding: '16px',
}}
>
<h2>Smooth Scrolling</h2>
{Array.from({ length: 50 }).map((_, index) => (
<p key={index}>Scroll content item {index + 1}</p>
))}
</div>
);
}TypeScript Usage
The hook provides full TypeScript support with proper type inference:
import { useRef, useState } from 'react';
import useEventListener from '@aniruddha1806/use-event-listener';
interface MousePosition {
x: number;
y: number;
}
function TypedEventExample() {
// Properly typed state
const [mousePosition, setMousePosition] = useState<MousePosition>({ x: 0, y: 0 });
const [clicks, setClicks] = useState<number>(0);
// Ref with proper HTML element type
const buttonRef = useRef<HTMLButtonElement>(null);
// Window event with proper event type
useEventListener(window, 'mousemove', (event: MouseEvent) => {
setMousePosition({ x: event.clientX, y: event.clientY });
});
// Element event with proper event type
useEventListener(buttonRef, 'click', (event: MouseEvent) => {
// event is properly typed as MouseEvent
setClicks(prev => prev + 1);
console.log('Click position:', event.clientX, event.clientY);
});
// Document event with proper event type
useEventListener(document, 'visibilitychange', (event: Event) => {
console.log('Visibility changed:', document.visibilityState);
});
return (
<div>
<h2>TypeScript Event Handling</h2>
<p>
Mouse position: {mousePosition.x}, {mousePosition.y}
</p>
<button ref={buttonRef}>
Click me ({clicks} clicks)
</button>
</div>
);
}Advanced Usage
Event Delegation
Implement event delegation pattern:
import { useRef } from 'react';
import useEventListener from '@aniruddha1806/use-event-listener';
function EventDelegation() {
const listRef = useRef(null);
// Handle clicks on any list item through event delegation
useEventListener(listRef, 'click', (event) => {
// Find the closest list item that was clicked
const listItem = event.target.closest('li');
if (listItem) {
console.log('List item clicked:', listItem.dataset.id);
// Highlight the clicked item
listItem.style.backgroundColor = '#f0f0f0';
setTimeout(() => {
listItem.style.backgroundColor = '';
}, 300);
}
});
return (
<div>
<h2>Event Delegation</h2>
<ul ref={listRef} style={{ listStyle: 'none', padding: 0 }}>
{Array.from({ length: 10 }).map((_, index) => (
<li
key={index}
data-id={index + 1}
style={{
padding: '8px 16px',
margin: '4px 0',
border: '1px solid #ddd',
borderRadius: '4px',
cursor: 'pointer',
transition: 'background-color 0.2s',
}}
>
Item {index + 1}
</li>
))}
</ul>
</div>
);
}Conditional Event Listeners
Add or remove event listeners conditionally:
import { useState } from 'react';
import useEventListener from '@aniruddha1806/use-event-listener';
function ConditionalEvents() {
const [isListening, setIsListening] = useState(false);
const [events, setEvents] = useState([]);
// Only add the event listener when isListening is true
useEventListener(
isListening ? window : null,
'mousemove',
(event) => {
setEvents((prev) => [
...prev.slice(-4),
{ x: event.clientX, y: event.clientY, time: new Date().toLocaleTimeString() },
]);
},
{ passive: true }
);
return (
<div>
<h2>Conditional Event Listener</h2>
<button onClick={() => setIsListening(!isListening)}>
{isListening ? 'Stop Listening' : 'Start Listening'}
</button>
<p>Status: {isListening ? 'Tracking mouse movement' : 'Not tracking'}</p>
<h3>Recent Mouse Events:</h3>
<ul>
{events.map((event, index) => (
<li key={index}>
Position: ({event.x}, {event.y}) at {event.time}
</li>
))}
</ul>
</div>
);
}