# useEventListener > Subscribe to a window, document or element event with cleanup. - Category: DOM - Package: `hookli` (install with `npm i hookli`) - Docs: https://hookli.vercel.app/docs/use-event-listener ## Signature ```ts useEventListener(eventName: K, handler: (event: Event) => void, element?: RefObject, options?: boolean | AddEventListenerOptions): void ``` ## Parameters - `eventName`: `K` — The event to listen for, typed against the target's event map (window, document, media query, or element). - `handler`: `(event) => void` — Called with the typed event on every dispatch. Held in a ref, so updating it never detaches and re-attaches the listener. - `element`: `RefObject` (default: `window`) — Optional ref to the target. Defaults to window when omitted. - `options`: `boolean | AddEventListenerOptions` — Standard addEventListener options (capture, passive, once). ## Usage ```tsx import { useRef, useState } from "react"; import { useEventListener } from "hookli"; export function Demo() { const ref = useRef(null); const [lastKey, setLastKey] = useState(""); useEventListener("keydown", (event) => setLastKey(event.key)); useEventListener("click", () => console.log("clicked"), ref); return
Last key: {lastKey}
; } ``` ## Source `src/hooks/use-event-listener/use-event-listener.ts` ```ts import { RefObject, useEffect, useRef } from "react"; import { useIsomorphicLayoutEffect } from "../use-isomorphic-layout-effect/use-isomorphic-layout-effect"; function useEventListener( eventName: K, handler: (event: MediaQueryListEventMap[K]) => void, element: RefObject, options?: boolean | AddEventListenerOptions, ): void; function useEventListener( eventName: K, handler: (event: WindowEventMap[K]) => void, element?: undefined, options?: boolean | AddEventListenerOptions, ): void; function useEventListener( eventName: K, handler: (event: DocumentEventMap[K]) => void, element: RefObject, options?: boolean | AddEventListenerOptions, ): void; function useEventListener< K extends keyof HTMLElementEventMap & keyof SVGElementEventMap, T extends HTMLElement | SVGElement = HTMLDivElement, >( eventName: K, handler: (event: HTMLElementEventMap[K] | SVGElementEventMap[K]) => void, element: RefObject, options?: boolean | AddEventListenerOptions, ): void; function useEventListener( eventName: string, handler: (event: Event) => void, element?: RefObject, options?: boolean | AddEventListenerOptions, ) { // Hold the handler in a ref so updating it never detaches the listener. const savedHandler = useRef(handler); useIsomorphicLayoutEffect(() => { savedHandler.current = handler; }, [handler]); useEffect(() => { const targetElement = element?.current ?? window; if (!targetElement?.addEventListener) return; const listener = (event: Event) => savedHandler.current(event); targetElement.addEventListener(eventName, listener, options); return () => { targetElement.removeEventListener(eventName, listener, options); }; }, [eventName, element, options]); } export { useEventListener }; ```