use-event-listener
A React hook that makes it easy to attach DOM event listeners declaratively with automatic cleanup.
Features
- Flexible Targeting: Observe elements via the
targetprop orsetElementReffunction. - Auto Cleanup: Listeners are automatically managed on unmount or dependency changes.
- Reactive: Listeners intelligently reattach when
target,event, oroptionschange. - Conditional Binding: Attach listeners only when
target, ahandler, andshouldInjectEventare valid. - Full options support: Fully compatible with all
AddEventListenerOptionsincluding capture, once, passive, and signal.
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:
tsx
// ❌ 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
- Automatic cleanup ensures listeners are removed when:-
- Component unmounts
targetelement changeseventtype changes- Any of
optionsparams:- (shouldInjectEvent, capture, once, passive, signal) gets changed
tsx
// ✅ 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
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
ts
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
ts
export type UseEventListenerReturnValues = {
setElementRef: (elementNode: HTMLElement | null) => void
}Common Use Cases
- Adding dom events (e.g 'click', 'keydown', 'resize', 'scroll')
Usage Examples
Basic Click Handler
ts
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
ts
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
ts
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>
)
}Usage with setElementRef
Example
ts
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>
)
}