mwui
GitHub

useEventListener

A hook that attaches a typed event listener with automatic cleanup.

Move the pointer in here

Last key on window:

Installation

Usage

addEventListener in an effect is four lines of boilerplate that go wrong in two ways: the cleanup gets forgotten, or the handler is added to the dependency array and the listener is torn down and re-attached on every render. This hook fixes both, and keeps the event fully typed.

import { useEventListener } from "@/hooks/use-event-listener";

function ShortcutBar() {
  useEventListener("keydown", (event) => {
    if (event.key === "Escape") close();
  });

  return null;
}

With no target, the listener goes on windowevent is narrowed from WindowEventMap, so event.key type-checks and event.clientX does not.

Targets

Pass a ref or an element as the third argument. The ref does not need to be populated on the first render; the listener is attached in an effect.

const cardRef = React.useRef<HTMLDivElement>(null);

useEventListener("pointermove", (event) => setPoint({ x: event.clientX, y: event.clientY }), cardRef);
useEventListener("visibilitychange", () => setActive(!document.hidden), document);

The event type follows the target: HTMLElementEventMap for elements, DocumentEventMap for document, WindowEventMap for the default.

Options

OptionDescription
captureListen during the capture phase.
passivePromise never to call preventDefault — required for smooth touchmove/wheel.
onceRemove the listener after it fires once.
enabledDetach without unmounting. Flip it instead of conditionally calling the hook.

Behavior

  • The handler is read from a ref, so it always sees fresh props and state without re-subscribing. You never need to memoize what you pass in.
  • Nothing touches the DOM during render, so the hook is safe in server-rendered trees.
  • target resolution happens inside the effect, which is why window and document defaults do not break SSR.