typescriptbeginner

useEventListener Hook

Safely add and clean up DOM event listeners with a type-safe hook supporting window, document, and elements.

typescript
import { useEffect, useRef } from 'react';

type EventMap = WindowEventMap & DocumentEventMap & HTMLElementEventMap;

function useEventListener<K extends keyof EventMap>(
  eventName: K,
  handler: (event: EventMap[K]) => void,
  element?: HTMLElement | Window | Document | null,
  options?: boolean | AddEventListenerOptions,
) {
  const savedHandler = useRef(handler);

  useEffect(() => {
    savedHandler.current = handler;
  }, [handler]);

  useEffect(() => {
    const target = element ?? window;
    if (!target?.addEventListener) return;

    const listener = (event: Event) => savedHandler.current(event as EventMap[K]);
    target.addEventListener(eventName, listener, options);
    return () => target.removeEventListener(eventName, listener, options);
  }, [eventName, element, options]);
}

// Usage 1: Window events
function WindowResizeDemo() {
  useEventListener('resize', () => {
    console.log(`Window: ${window.innerWidth}x${window.innerHeight}`);
  });

  useEventListener('keydown', (e) => {
    if (e.key === 'Escape') console.log('Escape pressed');
  });

  return <div>Resize window or press Escape</div>;
}

// Usage 2: Element events
function ClickOutsideDemo() {
  const ref = useRef<HTMLDivElement>(null);

  useEventListener('mousedown', (e) => {
    if (ref.current && !ref.current.contains(e.target as Node)) {
      console.log('Clicked outside!');
    }
  }, typeof document !== 'undefined' ? document : null);

  return (
    <div ref={ref} className="bg-[#111] border border-white/10 p-4 rounded-xl">
      Click outside this box
    </div>
  );
}

export { useEventListener };

Use Cases

  • Window resize handling
  • Global keyboard events
  • Click outside detection

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.