useEventListener


Use EventListener with simplicity by React Hook.

Supports Window, Element and Document and custom events with almost the same parameters as the native . See examples below.

If you want to use your CustomEvent using Typescript, you have to declare the event type. Find witch kind of Event you want to extends:

  • MediaQueryListEventMap
  • WindowEventMap
  • HTMLElementEventMap
  • DocumentEventMap

Then declare your custom event:

declare global {
  interface DocumentEventMap {
    'my-custom-event': CustomEvent<{ exampleArg: string }>
  }
}

Usage

import { useRef } from 'react'

import { useEventListener } from 'reactchemy'

export default function Component() {
   // Define button ref
   const buttonRef = useRef<HTMLButtonElement>(null)
   const documentRef = useRef<Document>(document)

   const onScroll = (event: Event) => {
      console.log('window scrolled!', event)
   }

   const onClick = (event: Event) => {
      console.log('button clicked!', event)
   }

   const onVisibilityChange = (event: Event) => {
      console.log('doc visibility changed!', {
         isVisible: !document.hidden,
         event,
      })
   }

   // example with window based event
   useEventListener('scroll', onScroll)

   // example with document based event
   useEventListener('visibilitychange', onVisibilityChange, documentRef)

   // example with element based event
   useEventListener('click', onClick, buttonRef)

   return (
    <div style={{ minHeight: '200vh' }}>
      <button ref={buttonRef}>Click me</button>
    </div>
   )
}

Hook

import type { RefObject } from 'react'
import { useEffect, useRef } from 'react'

import { useIsomorphicLayoutEffect } from 'reactchemy'

// MediaQueryList Event based useEventListener interface
function useEventListener<K extends keyof MediaQueryListEventMap>(
   eventName: K,
   handler: (event: MediaQueryListEventMap[K]) => void,
   element: RefObject<MediaQueryList>,
   options?: boolean | AddEventListenerOptions,
): void

// Window Event based useEventListener interface
function useEventListener<K extends keyof WindowEventMap>(
   eventName: K,
   handler: (event: WindowEventMap[K]) => void,
   element?: undefined,
   options?: boolean | AddEventListenerOptions,
): void

// Element Event based useEventListener interface
function useEventListener<
  K extends keyof HTMLElementEventMap,
  T extends HTMLElement = HTMLDivElement,
>(
   eventName: K,
   handler: (event: HTMLElementEventMap[K]) => void,
   element: RefObject<T>,
   options?: boolean | AddEventListenerOptions,
): void

// Document Event based useEventListener interface
function useEventListener<K extends keyof DocumentEventMap>(
   eventName: K,
   handler: (event: DocumentEventMap[K]) => void,
   element: RefObject<Document>,
   options?: boolean | AddEventListenerOptions,
): void

function useEventListener<
  KW extends keyof WindowEventMap,
  KH extends keyof HTMLElementEventMap,
  KM extends keyof MediaQueryListEventMap,
  T extends HTMLElement | MediaQueryList | void = void,
>(
   eventName: KW | KH | KM,
   handler: (
      event:
      | WindowEventMap[KW]
      | HTMLElementEventMap[KH]
      | MediaQueryListEventMap[KM]
      | Event,
   ) => void,
   element?: RefObject<T>,
   options?: boolean | AddEventListenerOptions,
) {
   // Create a ref that stores handler
   const savedHandler = useRef(handler)

   useIsomorphicLayoutEffect(() => {
      savedHandler.current = handler
   }, [handler])

   useEffect(() => {
      // Define the listening target
      const targetElement: T | Window = element?.current ?? window

      if (!(targetElement && targetElement.addEventListener))
         return

      // Create event listener that calls handler function stored in ref
      const listener: typeof handler = event => savedHandler.current(event)

      targetElement.addEventListener(eventName, listener, options)

      // Remove event listener on cleanup
      return () => {
         targetElement.removeEventListener(eventName, listener, options)
      }
   }, [eventName, element, options])
}

export { useEventListener }