Martin Myhre

Event listener hook for react

· 1 min read · #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.

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

useEventListener("paste", handlePaste, codesRef);

← All posts