A React hook for adding event listeners to DOM elements with automatic cleanup
npm install @usefy/use-event-listener

A React hook for adding event listeners to DOM elements with automatic cleanup
Installation •
Quick Start •
API Reference •
Examples •
License
---
@usefy/use-event-listener provides a simple way to add event listeners to DOM elements with automatic cleanup on unmount. It supports window, document, HTMLElement, and RefObject targets with full TypeScript type inference.
Part of the @usefy ecosystem — a collection of production-ready React hooks designed for modern applications.
- Zero Dependencies — Pure React implementation with no external dependencies
- TypeScript First — Full type safety with automatic event type inference
- Multiple Targets — Support for window, document, HTMLElement, and RefObject
- Automatic Cleanup — Event listeners are removed on unmount
- Handler Stability — No re-registration when handler changes
- Conditional Activation — Enable/disable via the enabled option
- Performance Options — Support for passive, capture, and once options
- SSR Compatible — Works seamlessly with Next.js, Remix, and other SSR frameworks
- Well Tested — Comprehensive test coverage with Vitest
---
``bashnpm
npm install @usefy/use-event-listener
$3
This package requires React 18 or 19:
`json
{
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0"
}
}
`---
Quick Start
`tsx
import { useEventListener } from "@usefy/use-event-listener";function WindowResizeTracker() {
const [size, setSize] = useState({ width: 0, height: 0 });
useEventListener("resize", () => {
setSize({ width: window.innerWidth, height: window.innerHeight });
});
return (
Window size: {size.width} × {size.height}
);
}
`---
API Reference
$3
A hook that adds an event listener to the specified target.
#### Parameters
| Parameter | Type | Description |
| ----------- | ------------------------- | ------------------------------------------------------ |
|
eventName | string | The event type to listen for (e.g., "click", "resize") |
| handler | (event: Event) => void | Callback function called when the event fires |
| element | EventTargetType | Target element (defaults to window) |
| options | UseEventListenerOptions | Configuration options |#### Options
| Option | Type | Default | Description |
| --------- | --------- | ----------- | ------------------------------------------ |
|
enabled | boolean | true | Whether the event listener is active |
| capture | boolean | false | Use event capture phase instead of bubble |
| passive | boolean | undefined | Use passive event listener for performance |
| once | boolean | false | Handler is invoked once and then removed |#### Supported Target Types
`typescript
type EventTargetType =
| Window // window object
| Document // document object
| HTMLElement // any HTML element
| React.RefObject // React ref
| null // no listener
| undefined; // defaults to window
`#### Returns
void---
Examples
$3
When no element is provided, events are attached to the window:
`tsx
import { useEventListener } from "@usefy/use-event-listener";function ResizeHandler() {
useEventListener("resize", (e) => {
console.log("Window resized:", window.innerWidth, window.innerHeight);
});
return
Resize the window;
}
`$3
`tsx
import { useEventListener } from "@usefy/use-event-listener";function KeyboardHandler() {
useEventListener(
"keydown",
(e) => {
if (e.key === "Escape") {
console.log("Escape pressed");
}
},
document
);
return
Press Escape;
}
`$3
`tsx
import { useEventListener } from "@usefy/use-event-listener";function ElementClickHandler() {
const button = document.getElementById("myButton");
useEventListener(
"click",
(e) => {
console.log("Button clicked");
},
button
);
return ;
}
`$3
`tsx
import { useEventListener } from "@usefy/use-event-listener";
import { useRef } from "react";function ScrollableBox() {
const boxRef = useRef(null);
const [scrollTop, setScrollTop] = useState(0);
useEventListener(
"scroll",
() => {
if (boxRef.current) {
setScrollTop(boxRef.current.scrollTop);
}
},
boxRef,
{ passive: true }
);
return (
Scroll position: {scrollTop}px
);
}
`$3
`tsx
import { useEventListener } from "@usefy/use-event-listener";function ConditionalListener() {
const [isListening, setIsListening] = useState(true);
useEventListener(
"click",
() => {
console.log("Clicked!");
},
document,
{ enabled: isListening }
);
return (
);
}
`$3
Use
passive: true for better scroll performance:`tsx
import { useEventListener } from "@usefy/use-event-listener";function OptimizedScrollHandler() {
useEventListener(
"scroll",
(e) => {
// Handle scroll without blocking
console.log("Scroll position:", window.scrollY);
},
window,
{ passive: true }
);
return
Scroll the page;
}
`$3
`tsx
import { useEventListener } from "@usefy/use-event-listener";function OneTimeHandler() {
useEventListener(
"click",
() => {
console.log("This only fires once!");
},
document,
{ once: true }
);
return
Click anywhere (only works once);
}
`$3
`tsx
import { useEventListener } from "@usefy/use-event-listener";function MultipleListeners() {
const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
const [isPressed, setIsPressed] = useState(false);
useEventListener("mousemove", (e) => {
setMousePos({ x: e.clientX, y: e.clientY });
});
useEventListener("mousedown", () => {
setIsPressed(true);
});
useEventListener("mouseup", () => {
setIsPressed(false);
});
return (
Position: ({mousePos.x}, {mousePos.y})
{isPressed ? "Mouse down" : "Mouse up"}
);
}
`$3
`tsx
import { useEventListener } from "@usefy/use-event-listener";function NetworkStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEventListener("online", () => setIsOnline(true));
useEventListener("offline", () => setIsOnline(false));
return
Network: {isOnline ? "Online" : "Offline"};
}
`---
TypeScript
This hook provides full type inference for event types:
`tsx
import { useEventListener } from "@usefy/use-event-listener";
import type {
UseEventListenerOptions,
EventTargetType,
} from "@usefy/use-event-listener";// MouseEvent is automatically inferred
useEventListener("click", (e) => {
console.log(e.clientX, e.clientY); // e is MouseEvent
});
// KeyboardEvent is automatically inferred
useEventListener(
"keydown",
(e) => {
console.log(e.key); // e is KeyboardEvent
},
document
);
// FocusEvent is automatically inferred
useEventListener(
"focus",
(e) => {
console.log(e.relatedTarget); // e is FocusEvent
},
inputRef
);
// Options type
const options: UseEventListenerOptions = {
enabled: true,
capture: false,
passive: true,
once: false,
};
``---
This package maintains comprehensive test coverage to ensure reliability and stability.
📊 View Detailed Coverage Report (GitHub Pages)
Basic Functionality Tests
- Add event listener to window by default
- Call handler when event fires
- Add event listener to document
- Add event listener to HTMLElement
- Add event listener to RefObject
- Handle multiple events
Enabled Option Tests
- Not add listener when enabled is false
- Add listener when enabled changes to true
- Remove listener when enabled changes to false
- Default enabled to true
Capture Option Tests
- Use capture phase when capture is true
- Use bubble phase when capture is false
- Re-register listener when capture changes
Passive Option Tests
- Pass passive: true to addEventListener
- Pass passive: false to addEventListener
- Use browser default when passive is undefined
Once Option Tests
- Pass once: true to addEventListener
- Default once to false
Handler Stability Tests
- Not re-register listener when handler changes
- Call updated handler after change
Cleanup Tests
- Remove event listener on unmount
- Not call handler after unmount
- Remove listener with correct capture option
---
MIT © mirunamu
This package is part of the usefy monorepo.
---
Built with care by the usefy team