Skip to content

use-event-listener

A React hook which provides a simple and declarative way to add DOM event listeners with automatic cleanup.

Features

  • Auto cleanup: Automatic cleanup of events on unmount and dependency change
  • Reactive: Potentially re-attaches listeners on dependency change(target, event, options)
  • Conditional event: Conditional event support with feature flag. And listeners only get attached when:- target exists, handler is provided, and shouldInjectEvent is true
  • Standard options: Full support for all AddEventListenerOptions (capture, once, passive, signal)

Problem It Solves

Boilerplate Reduction
  • Problem: Manually managing event listeners in React components leads to verbose, repetitive and error-prone code with potential memory leaks.
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>
}

Solution:

  • 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
optionsEvOptionsundefinedEvent listener options and feature flags

Options Parameter

The options parameter accepts an object that extends the standard AddEventListenerOptions with an additional custom property for conditional event handling.

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 does not return anything.

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