Skip to content

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.

When no target is provided, the listener is attached to window.

import { useEventListener } from "@usels/core";
function Component() {
useEventListener("keydown", (ev) => {
console.log(ev.key);
});
return null;
}
import { useRef$, useEventListener } from "@usels/core";
function Component() {
const el$ = useRef$<HTMLDivElement>();
useEventListener(el$, "click", (ev) => {
console.log("clicked", ev.target);
});
return <div ref={el$} />;
}

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$} />;
useEventListener(el$, ["mouseenter", "mouseleave"], (ev) => {
console.log(ev.type);
});
useEventListener(el$, "click", [onClickA, onClickB]);
useEventListener(document, "visibilitychange", () => {
console.log(document.visibilityState);
});
useEventListener(el$, "scroll", onScroll, { passive: true });

The hook returns a cleanup function for imperative removal before unmount.

const stop = useEventListener("resize", onResize);
// remove the listener early
stop();

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 state
const [el, setEl] = useState<HTMLDivElement | null>(null);
useEventListener(el, "click", handler);
// ✅ listener is re-registered automatically when el$ changes
const el$ = useRef$<HTMLDivElement>();
useEventListener(el$, "click", handler);
export interface GeneralEventListener<E = Event> {
(evt: E): void;
}
export declare function useEventListener<E extends keyof WindowEventMap>(event: Arrayable<E>, listener: Arrayable<(ev: WindowEventMap[E]) => void>, options?: MaybeObservable<boolean | AddEventListenerOptions>): () => void;
export declare function useEventListener<E extends keyof WindowEventMap>(target: Window, event: Arrayable<E>, listener: Arrayable<(ev: WindowEventMap[E]) => void>, options?: MaybeObservable<boolean | AddEventListenerOptions>): () => void;
export declare function useEventListener<E extends keyof DocumentEventMap>(target: Document, event: Arrayable<E>, listener: Arrayable<(ev: DocumentEventMap[E]) => void>, options?: MaybeObservable<boolean | AddEventListenerOptions>): () => void;
export declare function useEventListener<E extends keyof HTMLElementEventMap>(target: MaybeElement | MaybeElement[] | null | undefined, event: Arrayable<E>, listener: Arrayable<(ev: HTMLElementEventMap[E]) => void>, options?: MaybeObservable<boolean | AddEventListenerOptions>): () => void;
export declare function useEventListener<EventType = Event>(target: Observable<unknown>, event: Arrayable<string>, listener: Arrayable<GeneralEventListener<EventType>>, options?: MaybeObservable<boolean | AddEventListenerOptions>): () => void;
export declare function useEventListener<EventType = Event>(target: EventTarget | null | undefined, event: Arrayable<string>, listener: Arrayable<GeneralEventListener<EventType>>, options?: MaybeObservable<boolean | AddEventListenerOptions>): () => void;

View on GitHub

  • tigerwest
  • a7392ab 2026-03-06 - feat(core,browser): add sync strategy hooks (tigerwest)