Martin Kulvedrøsten Myhre
12:30-10/25/2023
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);