useEventListener
React hook that attaches event listeners to DOM elements, the window, or media query lists with automatic cleanup and type safety.
Installation
npx shadcn@latest add https://www.shadcn.io/registry/use-event-listener.json
npx shadcn@latest add https://www.shadcn.io/registry/use-event-listener.json
pnpm dlx shadcn@latest add https://www.shadcn.io/registry/use-event-listener.json
bunx shadcn@latest add https://www.shadcn.io/registry/use-event-listener.json
Features
- Versatile targets - Attach to window, document, DOM elements, or media query lists
- Automatic cleanup - Event listeners are removed on unmount or dependency changes
- Type safety - Full TypeScript support with proper event type inference
- Performance optimized - Handler function is stored in ref to prevent unnecessary re-renders
- Multiple overloads - Different interfaces for different event targets
- Modern API - Uses standard addEventListener/removeEventListener
Usage
import { useRef } from "react"
import { useEventListener } from "@/hooks/use-event-listener"
function MyComponent() {
const buttonRef = useRef<HTMLButtonElement>(null)
// Window event
useEventListener("scroll", (event) => {
console.log("Window scrolled!", event)
})
// Element event
useEventListener("click", (event) => {
console.log("Button clicked!", event)
}, buttonRef)
return <button ref={buttonRef}>Click me</button>
}
API Reference
useEventListener
The hook has multiple overloads for different event targets:
Window Events
useEventListener<K extends keyof WindowEventMap>(
eventName: K,
handler: (event: WindowEventMap[K]) => void,
element?: undefined,
options?: boolean | AddEventListenerOptions,
): void
Element Events
useEventListener<K, T extends Element>(
eventName: K,
handler: (event: HTMLElementEventMap[K] | SVGElementEventMap[K]) => void,
element: RefObject<T>,
options?: boolean | AddEventListenerOptions,
): void
Document Events
useEventListener<K extends keyof DocumentEventMap>(
eventName: K,
handler: (event: DocumentEventMap[K]) => void,
element: RefObject<Document>,
options?: boolean | AddEventListenerOptions,
): void
MediaQueryList Events
useEventListener<K extends keyof MediaQueryListEventMap>(
eventName: K,
handler: (event: MediaQueryListEventMap[K]) => void,
element: RefObject<MediaQueryList>,
options?: boolean | AddEventListenerOptions,
): void
Usage Examples
Window Events
const [scrollY, setScrollY] = useState(0)
useEventListener("scroll", () => {
setScrollY(window.scrollY)
})
useEventListener("resize", () => {
console.log("Window resized to:", window.innerWidth, window.innerHeight)
})
Element Events
const divRef = useRef<HTMLDivElement>(null)
const [mousePos, setMousePos] = useState({ x: 0, y: 0 })
useEventListener("mousemove", (event) => {
setMousePos({ x: event.clientX, y: event.clientY })
}, divRef)
useEventListener("click", (event) => {
console.log("Div clicked at:", event.clientX, event.clientY)
}, divRef)
Document Events
const documentRef = useRef<Document>(document)
const [isVisible, setIsVisible] = useState(!document.hidden)
useEventListener("visibilitychange", () => {
setIsVisible(!document.hidden)
}, documentRef)
useEventListener("keydown", (event) => {
if (event.key === "Escape") {
console.log("Escape key pressed")
}
}, documentRef)
MediaQueryList Events
const mediaQueryRef = useRef(window.matchMedia("(max-width: 768px)"))
const [isMobile, setIsMobile] = useState(false)
useEventListener("change", (event) => {
setIsMobile(event.matches)
}, mediaQueryRef)
With Event Options
// Passive event listener for better performance
useEventListener("scroll", handleScroll, undefined, { passive: true })
// Once option - listener runs only once
useEventListener("load", handleLoad, undefined, { once: true })
// Capture phase
useEventListener("click", handleClick, elementRef, { capture: true })
Complex Event Handling
function KeyboardHandler() {
const [keys, setKeys] = useState<string[]>([])
useEventListener("keydown", (event) => {
setKeys(prev => [...prev, event.key].slice(-5)) // Keep last 5 keys
})
useEventListener("keyup", (event) => {
if (event.key === "Escape") {
setKeys([]) // Clear on escape
}
})
return <div>Last keys: {keys.join(", ")}</div>
}
Conditional Event Listeners
function ConditionalListener({ enabled }: { enabled: boolean }) {
const buttonRef = useRef<HTMLButtonElement>(null)
// Only attach listener when enabled
useEventListener(
"click",
() => console.log("Clicked!"),
enabled ? buttonRef : undefined
)
return <button ref={buttonRef}>Click me</button>
}
Common Use Cases
- Scroll tracking - Monitor window or element scroll positions
- Keyboard shortcuts - Global or element-specific key handlers
- Mouse interactions - Track mouse movements, clicks, hover states
- Visibility tracking - Detect when page/elements become visible
- Resize handling - Respond to window or element size changes
- Media queries - React to breakpoint changes programmatically
- Form interactions - Handle input, focus, blur events
- Touch events - Mobile touch gesture handling
Implementation Details
- Handler function is stored in a ref to prevent unnecessary effect re-runs
- Uses
useIsomorphicLayoutEffect
to update handler ref synchronously - Automatically adds and removes event listeners based on dependencies
- Falls back to window when no element is provided
- Supports all standard
addEventListener
options - Provides full TypeScript intellisense for event types and properties
- Handles cleanup automatically on unmount or dependency changes
useEventCallback
Memoized event callback creation with fresh closure access. Perfect for React applications requiring stable callbacks with Next.js integration and TypeScript support.
useHover
Track mouse hover state on DOM elements with precise event detection. Perfect for React applications requiring hover interactions with Next.js integration and TypeScript support.