import { useEffect, useRef, RefObject, useLayoutEffect } from "react"; // https://usehooks-ts.com/react-hook/use-event-listener // MediaQueryList Event based useEventListener interface export function useEventListener( eventName: K, handler: (event: MediaQueryListEventMap[K]) => void, element: RefObject, options?: boolean | AddEventListenerOptions, ): void; // Window Event based useEventListener interface export function useEventListener( eventName: K, handler: (event: WindowEventMap[K]) => void, element?: undefined, options?: boolean | AddEventListenerOptions, ): void; // Element Event based useEventListener interface export function useEventListener< K extends keyof HTMLElementEventMap, T extends HTMLElement = HTMLDivElement, >( eventName: K, handler: (event: HTMLElementEventMap[K]) => void, element: RefObject, options?: boolean | AddEventListenerOptions, ): void; // Document Event based useEventListener interface export function useEventListener( eventName: K, handler: (event: DocumentEventMap[K]) => void, element: RefObject, options?: boolean | AddEventListenerOptions, ): void; export 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, options?: boolean | AddEventListenerOptions, ) { // Create a ref that stores handler const savedHandler = useRef(handler); useLayoutEffect(() => { 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 export 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]); }