use-event-listener
use-event-listener is a declarative React hook that simplifies DOM event handling with automatic lifecycle management.
It eliminates repetitive add/removeEventListener boilerplate while preventing memory leaks through intelligent cleanup on unmount or dependency changes.
This hook supports flexible targeting via either a target prop or setElementRef callback, and features built-in conditional binding. It fully supports all standard AddEventListenerOptions including capture, once, passive, and signal.
Features
- Declarative event handling: Attach DOM events via a clean, hook-based API
- Flexible targeting: Bind listeners using a
targetfunction orsetElementRefcallback - Automatic cleanup: Listeners are removed on unmount or dependency changes
- Reactive rebinding: Reattaches automatically when target, event, or options change
- Conditional binding: Toggle listener attachment with
shouldInjectEvent - Full options support: Supports all
AddEventListenerOptions(capture,once,passive,signal) - Stable references: Avoids unnecessary add/remove cycles across re-renders
- Ref-free usage: No manual refs needed when using
setElementRef
Usage Note
- Do not pass
targetprop if usingsetElementRefand vise-versa.
Problem It Solves
Boilerplate Reduction
Manually managing event listeners in React components leads to verbose, repetitive and error-prone code with potential memory leaks. See the below implementation:
// ❌ Problematic approach which is redundant and verbose
function Component() {
const [scrollY, setScrollY] = useState(0)
useEffect(() => {
const handleScroll = () => {
setScrollY(window.scrollY)
}
window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll) // Doing proper cleanup on unmount
}, [])
return <div>Current: {scrollY}</div>
}How use-event-listener solves it:
- Eliminates repetitive
addEventListener/removeEventListenercode - Reduces component complexity by abstracting event handling logic
- Makes conditional event attachment predictable and declarative
- Simplifies dynamic event binding when below things happens:-
- Component unmounts
targetelement changeseventtype changes- Any of
optionsparams:- (shouldInjectEvent, capture, once, passive, signal) gets changed
// ✅ Clean, declarative approach
function Component() {
const [scrollY, setScrollY] = useState(0)
const breakpoint = useEventListener({
target: () => window,
event: 'scroll',
handler: () => {
setScrollY(window.scrollY)
},
})
return <div>Current: {scrollY}</div>
}Performance Benefits
- Stable references accross re-renders which prevents listeners from being repeatedly added/removed
- Efficient dependency tracking
- Prevents memory leaks caused by missed or incorrect cleanup
- Eliminates manual lifecycle management for DOM event listeners
- Avoids unnecessary listener re-creation across component re-renders
Parameters
| Parameter | Type | Required | Default Value | Description |
|---|---|---|---|---|
| target | EvTarget | ✅ | - | Target element on which the event is listened to. |
| event | string | ✅ | - | Event name (e.g. 'click', 'keydown') |
| handler | EvHandler | ❌ | undefined | Event listener callback function |
| options | EvOptions | ❌ | undefined | Standard Options and Feature flags |
Options Parameter
The options parameter supports all standard AddEventListenerOptions and introduces extra custom properties to control conditional event binding.
Standard AddEventListenerOptions
| Property | Type | Default | Description |
|---|---|---|---|
| capture | boolean | false | If true, the listener will be triggered during the capture phase |
| once | boolean | false | If true, the listener will be automatically removed after being triggered once |
| passive | boolean | false | If true, indicates that the function will never call preventDefault() |
| signal | AbortSignal | undefined | An AbortSignal that can be used to remove the event listener |
Custom Options
| Property | Type | Default | Description |
|---|---|---|---|
| shouldInjectEvent | boolean | any | true | Controls whether the event listener should be attached. When false, the event listener is not added |
Type Definitions
Details
export type EvTarget = () => EventTarget | null
export type EvHandler = (event: Event) => void
export interface EvOptions extends AddEventListenerOptions {
// Standard AddEventListenerOptions:
capture?: boolean
once?: boolean
passive?: boolean
signal?: AbortSignal
// Custom option:
shouldInjectEvent?: boolean | any // Controls whether the event should be attached
}Return Value(s)
This hook returns an object that includes a setter function, allowing you to observe and manage the target element through its ref attribute.
| Property | Type | Description |
|---|---|---|
| setElementRef | Function | A ref callback that observes the target element for event listening |
Return Types
Details
export type UseEventListenerReturnValues = {
setElementRef: (elementNode: HTMLElement | null) => void
}Common Use Cases
- Adding dom events (e.g 'click', 'keydown', 'resize', 'scroll')
Usage Examples
Usage with setElementRef(no manual creation of ref)
import { useState } from 'react'
import { useEventListener } from 'classic-react-hooks'
export default function ConditionalExample() {
const [counter, setCounter] = useState(0)
const { setElementRef } = useEventListener({
event: 'click',
handler: () => {
console.log(counter)
},
})
return (
<div>
<button onClick={() => setCounter((c) => c + 1)}>update counter {counter}</button>
<div ref={setElementRef}>log value</div>
</div>
)
}Basic Click Handler
Exampls
import { useRef } from 'react'
import { useEventListener } from 'classic-react-hooks'
export default function ClickExample() {
const buttonRef = useRef<HTMLButtonElement>(null)
useEventListener({
target: () => buttonRef.current,
event: 'click',
handler: (e) => {
console.log('Button clicked!', e)
},
})
return <button ref={buttonRef}>Click me</button>
}Listening Window Event
Example
import { useEventListener } from 'classic-react-hooks'
export default function WindowExample() {
useEventListener({
target: () => window,
event: 'resize',
handler: (e) => {
console.log('Window resized:', window.innerWidth, window.innerHeight)
},
})
return <div>Resize the window and check console</div>
}Conditional Event Listening
Example
import { useState } from 'react'
import { useEventListener } from 'classic-react-hooks'
export default function ConditionalExample() {
const [isListening, setIsListening] = useState(true)
useEventListener({
target: () => document,
event: 'keydown',
handler: (e) => {
console.log('Key pressed:', e.key)
},
options: {
shouldInjectEvent: isListening, // Only listen when enabled
},
})
return (
<div>
<button onClick={() => setIsListening(!isListening)}>{isListening ? 'Stop' : 'Start'} Listening</button>
<p>Press any key (when listening is enabled)</p>
</div>
)
}