npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@usefy/use-event-listener

v0.0.31

Published

A React hook for adding event listeners to DOM elements with automatic cleanup

Readme


Overview

@usefy/use-event-listener provides a simple way to add event listeners to DOM elements with automatic cleanup on unmount. It supports window, document, HTMLElement, and RefObject targets with full TypeScript type inference.

Part of the @usefy ecosystem — a collection of production-ready React hooks designed for modern applications.

Why use-event-listener?

  • Zero Dependencies — Pure React implementation with no external dependencies
  • TypeScript First — Full type safety with automatic event type inference
  • Multiple Targets — Support for window, document, HTMLElement, and RefObject
  • Automatic Cleanup — Event listeners are removed on unmount
  • Handler Stability — No re-registration when handler changes
  • Conditional Activation — Enable/disable via the enabled option
  • Performance Options — Support for passive, capture, and once options
  • SSR Compatible — Works seamlessly with Next.js, Remix, and other SSR frameworks
  • Well Tested — Comprehensive test coverage with Vitest

Installation

# npm
npm install @usefy/use-event-listener

# yarn
yarn add @usefy/use-event-listener

# pnpm
pnpm add @usefy/use-event-listener

Peer Dependencies

This package requires React 18 or 19:

{
  "peerDependencies": {
    "react": "^18.0.0 || ^19.0.0"
  }
}

Quick Start

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

function WindowResizeTracker() {
  const [size, setSize] = useState({ width: 0, height: 0 });

  useEventListener("resize", () => {
    setSize({ width: window.innerWidth, height: window.innerHeight });
  });

  return (
    <div>
      Window size: {size.width} × {size.height}
    </div>
  );
}

API Reference

useEventListener(eventName, handler, element?, options?)

A hook that adds an event listener to the specified target.

Parameters

| Parameter | Type | Description | | ----------- | ------------------------- | ------------------------------------------------------ | | eventName | string | The event type to listen for (e.g., "click", "resize") | | handler | (event: Event) => void | Callback function called when the event fires | | element | EventTargetType | Target element (defaults to window) | | options | UseEventListenerOptions | Configuration options |

Options

| Option | Type | Default | Description | | --------- | --------- | ----------- | ------------------------------------------ | | enabled | boolean | true | Whether the event listener is active | | capture | boolean | false | Use event capture phase instead of bubble | | passive | boolean | undefined | Use passive event listener for performance | | once | boolean | false | Handler is invoked once and then removed |

Supported Target Types

type EventTargetType<T extends HTMLElement = HTMLElement> =
  | Window // window object
  | Document // document object
  | HTMLElement // any HTML element
  | React.RefObject<T> // React ref
  | null // no listener
  | undefined; // defaults to window

Returns

void


Examples

Window Events (Default)

When no element is provided, events are attached to the window:

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

function ResizeHandler() {
  useEventListener("resize", (e) => {
    console.log("Window resized:", window.innerWidth, window.innerHeight);
  });

  return <div>Resize the window</div>;
}

Document Events

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

function KeyboardHandler() {
  useEventListener(
    "keydown",
    (e) => {
      if (e.key === "Escape") {
        console.log("Escape pressed");
      }
    },
    document
  );

  return <div>Press Escape</div>;
}

HTMLElement Events

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

function ElementClickHandler() {
  const button = document.getElementById("myButton");

  useEventListener(
    "click",
    (e) => {
      console.log("Button clicked");
    },
    button
  );

  return <button id="myButton">Click me</button>;
}

RefObject Events

import { useEventListener } from "@usefy/use-event-listener";
import { useRef } from "react";

function ScrollableBox() {
  const boxRef = useRef<HTMLDivElement>(null);
  const [scrollTop, setScrollTop] = useState(0);

  useEventListener(
    "scroll",
    () => {
      if (boxRef.current) {
        setScrollTop(boxRef.current.scrollTop);
      }
    },
    boxRef,
    { passive: true }
  );

  return (
    <div ref={boxRef} style={{ height: 200, overflow: "auto" }}>
      <div style={{ height: 1000 }}>Scroll position: {scrollTop}px</div>
    </div>
  );
}

Conditional Activation

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

function ConditionalListener() {
  const [isListening, setIsListening] = useState(true);

  useEventListener(
    "click",
    () => {
      console.log("Clicked!");
    },
    document,
    { enabled: isListening }
  );

  return (
    <button onClick={() => setIsListening(!isListening)}>
      {isListening ? "Disable" : "Enable"} Listener
    </button>
  );
}

Passive Scroll Listener

Use passive: true for better scroll performance:

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

function OptimizedScrollHandler() {
  useEventListener(
    "scroll",
    (e) => {
      // Handle scroll without blocking
      console.log("Scroll position:", window.scrollY);
    },
    window,
    { passive: true }
  );

  return <div>Scroll the page</div>;
}

One-time Event Handler

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

function OneTimeHandler() {
  useEventListener(
    "click",
    () => {
      console.log("This only fires once!");
    },
    document,
    { once: true }
  );

  return <div>Click anywhere (only works once)</div>;
}

Multiple Event Listeners

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

function MultipleListeners() {
  const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
  const [isPressed, setIsPressed] = useState(false);

  useEventListener("mousemove", (e) => {
    setMousePos({ x: e.clientX, y: e.clientY });
  });

  useEventListener("mousedown", () => {
    setIsPressed(true);
  });

  useEventListener("mouseup", () => {
    setIsPressed(false);
  });

  return (
    <div>
      Position: ({mousePos.x}, {mousePos.y})
      <br />
      {isPressed ? "Mouse down" : "Mouse up"}
    </div>
  );
}

Network Status

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

function NetworkStatus() {
  const [isOnline, setIsOnline] = useState(navigator.onLine);

  useEventListener("online", () => setIsOnline(true));
  useEventListener("offline", () => setIsOnline(false));

  return <div>Network: {isOnline ? "Online" : "Offline"}</div>;
}

TypeScript

This hook provides full type inference for event types:

import { useEventListener } from "@usefy/use-event-listener";
import type {
  UseEventListenerOptions,
  EventTargetType,
} from "@usefy/use-event-listener";

// MouseEvent is automatically inferred
useEventListener("click", (e) => {
  console.log(e.clientX, e.clientY); // e is MouseEvent
});

// KeyboardEvent is automatically inferred
useEventListener(
  "keydown",
  (e) => {
    console.log(e.key); // e is KeyboardEvent
  },
  document
);

// FocusEvent is automatically inferred
useEventListener(
  "focus",
  (e) => {
    console.log(e.relatedTarget); // e is FocusEvent
  },
  inputRef
);

// Options type
const options: UseEventListenerOptions = {
  enabled: true,
  capture: false,
  passive: true,
  once: false,
};

Testing

This package maintains comprehensive test coverage to ensure reliability and stability.

Test Coverage

📊 View Detailed Coverage Report (GitHub Pages)

Test Categories

  • Add event listener to window by default
  • Call handler when event fires
  • Add event listener to document
  • Add event listener to HTMLElement
  • Add event listener to RefObject
  • Handle multiple events
  • Not add listener when enabled is false
  • Add listener when enabled changes to true
  • Remove listener when enabled changes to false
  • Default enabled to true
  • Use capture phase when capture is true
  • Use bubble phase when capture is false
  • Re-register listener when capture changes
  • Pass passive: true to addEventListener
  • Pass passive: false to addEventListener
  • Use browser default when passive is undefined
  • Pass once: true to addEventListener
  • Default once to false
  • Not re-register listener when handler changes
  • Call updated handler after change
  • Remove event listener on unmount
  • Not call handler after unmount
  • Remove listener with correct capture option

License

MIT © mirunamu

This package is part of the usefy monorepo.