useEventListener
Registers an event listener with addEventListener on mount and automatically removes it with removeEventListener on unmount. Targets can be Ref, MaybeElement, a plain Element, Window, or Document. The listener is always called with the latest closure value — state changes never cause stale callbacks.
Window (default target)
Section titled “Window (default target)”When no target is provided, the listener is attached to window.
import { const useEventListener: { <E extends keyof WindowEventMap>(event: Arrayable<E>, listener: Arrayable<(ev: WindowEventMap[E]) => void>, options?: MaybeObservable<boolean | AddEventListenerOptions>): () => void; <E extends keyof WindowEventMap>(target: Window | Observable<OpaqueObject<Window> | null>, event: Arrayable<E>, listener: Arrayable<(ev: WindowEventMap[E]) => void>, options?: MaybeObservable<boolean | undefined | AddEventListenerOptions>): () => void; <E extends keyof DocumentEventMap>(target: Document | Observable<OpaqueObject<Document> | null>, event: Arrayable<E>, listener: Arrayable<(ev: DocumentEventMap[E]) => void>, options?: MaybeObservable<boolean | AddEventListenerOptions>): () => void; <E extends keyof HTMLElementEventMap>(target: MaybeEventTarget | MaybeEventTarget[] | null | undefined, event: Arrayable<E>, listener: Arrayable<(ev: HTMLElementEventMap[E]) => void>, options?: MaybeObservable<boolean | AddEventListenerOptions>): () => void; <EventType = Event>(target: Observable<unknown> | ObservablePrimitive<unknown> | ReadonlyObservable<unknown> | null | undefined, event: Arrayable<string>, listener: Arrayable<GeneralEventListener<EventType>>, options?: MaybeObservable<boolean | AddEventListenerOptions>): () => void; <EventType = Event>(target: MaybeEventTarget | EventTarget | null | undefined, event: Arrayable<string>, listener: Arrayable<GeneralEventListener<EventType>>, options?: MaybeObservable<boolean | AddEventListenerOptions>): () => void; <EventType = Event>(target: EventTarget | null | undefined, event: Arrayable<string>, listener: Arrayable<GeneralEventListener<EventType>>, options?: MaybeObservable<boolean | AddEventListenerOptions>): () => void;}
useEventListener } from "@usels/web";
function function Component(): null
Component() { useEventListener<"keydown">(event: Arrayable<"keydown">, listener: Arrayable<(ev: WindowEventMap["keydown"]) => void>, options?: MaybeObservable<boolean | AddEventListenerOptions>): () => void (+6 overloads)
Framework-agnostic event listener registration. Must be called inside a
useScope factory — registration is deferred to onMount so that any
other reactive setup in the same scope (e.g. useWhenever) gets to run
first. Reactive Ref$ / Observable<EventTarget> targets are tracked
via legendObserve and re-bound automatically when they change.
Overload 1: Omitted target — defaults to window.
useEventListener("keydown", (ev: WindowEventMap
ev) => { any
console.any
log(ev: WindowEventMap
ev.any
key); });
return null;}import { createEventListener } from "@usels/web";
function Component() { "use scope" createEventListener("keydown", (ev) => { console.log(ev.key); });
return null;}Element target
Section titled “Element target”import { useRef$, useEventListener } from "@usels/web";
function Component() { const el$ = useRef$<HTMLDivElement>();
useEventListener(el$, "click", (ev) => { console.log("clicked", ev.target); });
return <div ref={el$} />;}import { createEventListener } from "@usels/web";import { createRef$ } from "@usels/core";
function Component() { "use scope" const el$ = createRef$<HTMLDivElement>();
createEventListener(el$, "click", (ev) => { console.log("clicked", ev.target); });
return <div ref={el$} />;}Reactive Ref$ target
Section titled “Reactive Ref$ target”When an Ref$ or MaybeElement is passed as the target, the listener is automatically re-registered whenever the element changes.
const el$ = useRef$<HTMLButtonElement>();
useEventListener(el$, "pointerdown", (ev) => { ev.preventDefault();});
return <button ref={el$} />;const el$ = createRef$<HTMLButtonElement>();
createEventListener(el$, "pointerdown", (ev) => { ev.preventDefault();});
return <button ref={el$} />;Multiple events
Section titled “Multiple events”useEventListener(el$, ["mouseenter", "mouseleave"], (ev) => { console.log(ev.type);});createEventListener(el$, ["mouseenter", "mouseleave"], (ev) => { console.log(ev.type);});Multiple listeners
Section titled “Multiple listeners”tsx useEventListener(el$, "click", [onClickA, onClickB]); tsx createEventListener(el$, "click", [onClickA, onClickB]);
Document / Window target
Section titled “Document / Window target”useEventListener(document, "visibilitychange", () => { console.log(document.visibilityState);});createEventListener(document, "visibilitychange", () => { console.log(document.visibilityState);});AddEventListenerOptions
Section titled “AddEventListenerOptions”useEventListener(el$, "scroll", onScroll, { passive: true });createEventListener(el$, "scroll", onScroll, { passive: true });Manual cleanup
Section titled “Manual cleanup”The hook returns a cleanup function for imperative removal before unmount.
const stop = useEventListener("resize", onResize);
// remove the listener earlystop();const stop = createEventListener("resize", onResize);
// remove the listener earlystop();Plain element targets are not reactive. If you pass a plain HTMLElement or null value and that reference changes after mount (e.g. via useState), the hook does not detect the change. Use Ref$ or MaybeElement for targets that change over time.
// ❌ listener stays on the original element if el changes via stateconst [el, setEl] = useState<HTMLDivElement | null>(null);useEventListener(el, "click", handler);
// ✅ listener is re-registered automatically when el$ changesconst el$ = useRef$<HTMLDivElement>();useEventListener(el$, "click", handler);export interface GeneralEventListener<E = Event> { (evt: E): void;}/** * Single source of truth for the (target, event, listener, options) * positional layout. Both `createEventListener` and the React wrapper * call this so there is no duplicated arg-shape detection. */export interface ParsedEventListenerArgs { hasTarget: boolean; /** index of the listener parameter inside the original args array */ listenerIdx: number; target: any; event: any; listener: any; options: any;}export declare function parseEventListenerArgs(args: any[]): ParsedEventListenerArgs;/** * Framework-agnostic event listener registration. Must be called inside a * `useScope` factory — registration is deferred to `onMount` so that any * other reactive setup in the same scope (e.g. `useWhenever`) gets to run * first. Reactive `Ref$` / `Observable<EventTarget>` targets are tracked * via `legendObserve` and re-bound automatically when they change. * * Overload 1: Omitted target — defaults to `window`. */export declare function createEventListener<E extends keyof WindowEventMap>(event: Arrayable<E>, listener: Arrayable<(ev: WindowEventMap[E]) => void>, options?: MaybeObservable<boolean | AddEventListenerOptions>): () => void;/** * Overload 2: Explicit `Window` target. */export declare function createEventListener<E extends keyof WindowEventMap>(target: Window | Observable<OpaqueObject<Window> | null>, event: Arrayable<E>, listener: Arrayable<(ev: WindowEventMap[E]) => void>, options?: MaybeObservable<boolean | undefined | AddEventListenerOptions>): () => void;/** * Overload 3: Explicit or reactive `Document` target. */export declare function createEventListener<E extends keyof DocumentEventMap>(target: Document | Observable<OpaqueObject<Document> | null>, event: Arrayable<E>, listener: Arrayable<(ev: DocumentEventMap[E]) => void>, options?: MaybeObservable<boolean | AddEventListenerOptions>): () => void;/** * Overload 4: `MaybeEventTarget` target — supports Ref$, Observable<OpaqueObject<Element>>, * Document, Window, or an array of those (Legend-State reactive). */export declare function createEventListener<E extends keyof HTMLElementEventMap>(target: MaybeEventTarget | MaybeEventTarget[] | null | undefined, event: Arrayable<E>, listener: Arrayable<(ev: HTMLElementEventMap[E]) => void>, options?: MaybeObservable<boolean | AddEventListenerOptions>): () => void;/** * Overload 5: Observable<EventTarget> — reactive target (e.g. * Observable<MediaQueryList>, Ref$<HTMLElement>, etc.). */export declare function createEventListener<EventType = Event>(target: Observable<unknown> | ObservablePrimitive<unknown> | ReadonlyObservable<unknown> | null | undefined, event: Arrayable<string>, listener: Arrayable<GeneralEventListener<EventType>>, options?: MaybeObservable<boolean | AddEventListenerOptions>): () => void;/** * Overload 6: Combined `MaybeEventTarget | EventTarget` — handles union types where * the target may be either a reactive MaybeEventTarget or a raw EventTarget. */export declare function createEventListener<EventType = Event>(target: MaybeEventTarget | EventTarget | null | undefined, event: Arrayable<string>, listener: Arrayable<GeneralEventListener<EventType>>, options?: MaybeObservable<boolean | AddEventListenerOptions>): () => void;/** * Overload 7: Generic `EventTarget` fallback. */export declare function createEventListener<EventType = Event>(target: EventTarget | null | undefined, event: Arrayable<string>, listener: Arrayable<GeneralEventListener<EventType>>, options?: MaybeObservable<boolean | AddEventListenerOptions>): () => void;