Home

Event listener hook for react

MK

Martin Kulvedrøsten Myhre

12:30-10/25/2023

React

Events can be very hard to work with in react so i made a type safe hook that simplifies the process.

The last thing i want is to download a whole hook library just for one hook so the simplest thing is to add it yourself.

typescript

import { RefObject, useEffect, useRef, useLayoutEffect } from 'react';

// ----------------------------------------------------------------------

const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;

// 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,
 T extends HTMLElement | void = void,
>(
 eventName: KW | KH,
 handler: (event: WindowEventMap[KW] | HTMLElementEventMap[KH] | 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 eventListener: typeof handler = (event) => savedHandler.current(event);

  targetElement.addEventListener(eventName, eventListener, options);

  // Remove event listener on cleanup
  // eslint-disable-next-line consistent-return
  return () => {
   targetElement.removeEventListener(eventName, eventListener);
  };
 }, [eventName, element, options]);
}

export default useEventListener;

Usage:

tsx

useEventListener('paste', handlePaste, codesRef);