Skip to content

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 target prop or setElementRef function.
  • Auto Cleanup: Listeners are automatically managed on unmount or dependency changes.
  • Reactive: Listeners intelligently reattach when target, event, or options change.
  • Conditional Binding: Attach listeners only when target, a handler, and shouldInjectEvent are valid.
  • Full options support: Fully compatible with all AddEventListenerOptions including capture, once, passive, and signal.

Usage Note

  • Do not pass target prop if using setElementRef and 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/removeEventListener code
  • Reduces component complexity by abstracting event handling logic
  • Automatic cleanup ensures listeners are removed when:-
    • Component unmounts
    • target element changes
    • event type changes
    • Any of options params:- (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

ParameterTypeRequiredDefault ValueDescription
targetEvTarget-Target element on which the event is listened to.
eventstring-Event name (e.g. 'click', 'keydown')
handlerEvHandlerundefinedEvent listener callback function
optionsEvOptionsundefinedStandard 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

PropertyTypeDefaultDescription
capturebooleanfalseIf true, the listener will be triggered during the capture phase
oncebooleanfalseIf true, the listener will be automatically removed after being triggered once
passivebooleanfalseIf true, indicates that the function will never call preventDefault()
signalAbortSignalundefinedAn AbortSignal that can be used to remove the event listener

Custom Options

PropertyTypeDefaultDescription
shouldInjectEventboolean | anytrueControls 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.

PropertyTypeDescription
setElementRefFunctionA 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>
   )
}