DOM
useEventListener
Subscribe to a window, document or element event with cleanup.
Demo
live · imported from hookli
Click here, or press any key
- last key (window)
- —
- key presses
- 0
- panel clicks
- 0
import { useRef, useState } from "react";
import { useEventListener } from "hookli";
export function Demo() {
const ref = useRef<HTMLDivElement>(null);
const [lastKey, setLastKey] = useState("");
useEventListener("keydown", (event) => setLastKey(event.key));
useEventListener("click", () => console.log("clicked"), ref);
return <div ref={ref}>Last key: {lastKey}</div>;
}Usage
import { useRef, useState } from "react";
import { useEventListener } from "hookli";
export function Demo() {
const ref = useRef<HTMLDivElement>(null);
const [lastKey, setLastKey] = useState("");
useEventListener("keydown", (event) => setLastKey(event.key));
useEventListener("click", () => console.log("clicked"), ref);
return <div ref={ref}>Last key: {lastKey}</div>;
}API
useEventListener<K>(eventName: K, handler: (event: Event) => void, element?: RefObject<T>, options?: boolean | AddEventListenerOptions): voidParameters
| Name | Type | Default | Description |
|---|---|---|---|
| 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<T> | window | Optional ref to the target. Defaults to window when omitted. |
| options | boolean | AddEventListenerOptions | — | Standard addEventListener options (capture, passive, once). |
Returns
This hook returns nothing.
Hook
The implementation, straight from hookli. Copy it, or install the package.
import { RefObject, useEffect, useRef } from "react";
import { useIsomorphicLayoutEffect } from "../use-isomorphic-layout-effect/use-isomorphic-layout-effect";
function useEventListener<K extends keyof MediaQueryListEventMap>(
eventName: K,
handler: (event: MediaQueryListEventMap[K]) => void,
element: RefObject<MediaQueryList>,
options?: boolean | AddEventListenerOptions,
): void;
function useEventListener<K extends keyof WindowEventMap>(
eventName: K,
handler: (event: WindowEventMap[K]) => void,
element?: undefined,
options?: boolean | AddEventListenerOptions,
): void;
function useEventListener<K extends keyof DocumentEventMap>(
eventName: K,
handler: (event: DocumentEventMap[K]) => void,
element: RefObject<Document>,
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<T>,
options?: boolean | AddEventListenerOptions,
): void;
function useEventListener(
eventName: string,
handler: (event: Event) => void,
element?: RefObject<HTMLElement | MediaQueryList | Document>,
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 };