Supports Window, Element, Document, and MediaQueryList, with almost the same parameters as the native addEventListener options.
TypeScript
The hook is declared with several overloads. TypeScript picks one from:
- Window — omit the element argument.
eventNamemust be a key ofWindowEventMap; the handler receivesWindowEventMap[eventName]. - Document — pass a ref whose
currentisDocument(e.g.useRef<Document>(document)). Use keys ofDocumentEventMapforeventName. - HTMLElement / SVG — pass
useRef<HTMLButtonElement>(null)(or another element type). Use keys shared byHTMLElementEventMapandSVGElementEventMap. - Media query — pass a ref to a
MediaQueryListand usechange(seeMediaQueryListEventMap).
If the third argument is omitted, the listener attaches to window. If you pass a ref, attach to ref.current when it is non-null; otherwise the effect falls back to window (see implementation).
Custom events
If you want to use a CustomEvent with TypeScript, extend the right map:
MediaQueryListEventMapWindowEventMapHTMLElementEventMapDocumentEventMap
Example:
declare global {
interface DocumentEventMap {
'my-custom-event': CustomEvent<{ exampleArg: string }>
}
}
Usage
import { useRef } from 'react'
import { useEventListener } from './useEventListener'
export default function Component() {
const buttonRef = useRef<HTMLButtonElement>(null)
const documentRef = useRef<Document>(document)
const onScroll = (event: WindowEventMap['scroll']) => {
console.log('window scrolled!', event.type)
}
const onClick = (event: HTMLElementEventMap['click']) => {
console.log('button clicked!', event.currentTarget)
}
const onVisibilityChange = (event: DocumentEventMap['visibilitychange']) => {
console.log('doc visibility changed!', {
isVisible: !document.hidden,
event,
})
}
useEventListener('scroll', onScroll)
useEventListener('visibilitychange', onVisibilityChange, documentRef)
useEventListener('click', onClick, buttonRef)
return (
<div style={{ minHeight: '200vh' }}>
<button ref={buttonRef} type="button">
Click me
</button>
</div>
)
}
API
function useEventListener(eventName: K, handler: (event: unknown) => void, element: RefObject<MediaQueryList | null>, options?: boolean | AddEventListenerOptions): void
Parameters
| Name | Type | Default value | Description |
|---|---|---|---|
eventName | K | - | - |
handler | (event: unknown) => void | - | - |
element | `RefObject<MediaQueryList | null>` | - |
options? | `boolean | AddEventListenerOptions` | - |
Returns
No return description available yet.
Hook
import { useEffect, useRef } from 'react'
import type { RefObject } from 'react'
import { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect/useIsomorphicLayoutEffect'
// MediaQueryList Event based useEventListener interface
function useEventListener<K extends keyof MediaQueryListEventMap>(
eventName: K,
handler: (event: MediaQueryListEventMap[K]) => void,
element: RefObject<MediaQueryList | null>,
options?: boolean | AddEventListenerOptions,
): void
// Window Event based useEventListener interface
function useEventListener<K extends keyof WindowEventMap>(
eventName: K,
handler: (event: WindowEventMap[K]) => void,
element?: undefined,
options?: boolean | AddEventListenerOptions,
): void
// Element Event based useEventListener interface
function useEventListener<
K extends keyof HTMLElementEventMap & keyof SVGElementEventMap,
T extends Element = K extends keyof HTMLElementEventMap ? HTMLDivElement : SVGElement,
>(
eventName: K,
handler: ((event: HTMLElementEventMap[K]) => void) | ((event: SVGElementEventMap[K]) => void),
element: RefObject<T | null>,
options?: boolean | AddEventListenerOptions,
): void
// Document Event based useEventListener interface
function useEventListener<K extends keyof DocumentEventMap>(
eventName: K,
handler: (event: DocumentEventMap[K]) => void,
element: RefObject<Document | null>,
options?: boolean | AddEventListenerOptions,
): void
/**
* Custom hook that attaches event listeners to DOM elements, the window, or media query lists.
* @template KW - The type of event for window events.
* @template KH - The type of event for HTML or SVG element events.
* @template KM - The type of event for media query list events.
* @template T - The type of the DOM element (default is `HTMLElement`).
* @param {KW | KH | KM} eventName - The name of the event to listen for.
* @param {(event: WindowEventMap[KW] | HTMLElementEventMap[KH] | SVGElementEventMap[KH] | MediaQueryListEventMap[KM] | Event) => void} handler - The event handler function.
* @param {RefObject<T>} [element] - The DOM element or media query list to attach the event listener to (optional).
* @param {boolean | AddEventListenerOptions} [options] - An options object that specifies characteristics about the event listener (optional).
* @public
* @see [Documentation](https://usehooks-ts.com/react-hook/use-event-listener)
* @example
* ```tsx
* const handleResize = (event: WindowEventMap['resize']) => {
* console.log(window.innerWidth);
* };
* useEventListener('resize', handleResize);
* ```
* @example
* ```tsx
* const documentRef = useRef<Document>(document);
* const handleVisibility = (event: DocumentEventMap['visibilitychange']) => {
* console.log(document.hidden, event);
* };
* useEventListener('visibilitychange', handleVisibility, documentRef, { capture: true });
* ```
* @example
* ```tsx
* const buttonRef = useRef<HTMLButtonElement>(null);
* const handleClick = (event: HTMLElementEventMap['click']) => {
* console.log(event.currentTarget);
* };
* useEventListener('click', handleClick, buttonRef);
* ```
*/
function useEventListener<
KW extends keyof WindowEventMap,
KH extends keyof HTMLElementEventMap & keyof SVGElementEventMap,
KM extends keyof MediaQueryListEventMap,
T extends HTMLElement | SVGAElement | MediaQueryList = HTMLElement,
>(
eventName: KW | KH | KM,
handler: (
event:
| WindowEventMap[KW]
| HTMLElementEventMap[KH]
| SVGElementEventMap[KH]
| MediaQueryListEventMap[KM]
| Event,
) => void,
element?: RefObject<T | null>,
options?: boolean | AddEventListenerOptions,
) {
// Create a ref that stores handler
const savedHandler = useRef(handler)
useIsomorphicLayoutEffect(() => {
savedHandler.current = handler
}, [handler])
useEffect(() => {
// Define the listening target
const targetElement: T | Window = element?.current ?? window
if (!(targetElement && targetElement.addEventListener)) return
// Create event listener that calls handler function stored in ref
const listener: typeof handler = event => {
savedHandler.current(event)
}
targetElement.addEventListener(eventName, listener, options)
// Remove event listener on cleanup
return () => {
targetElement.removeEventListener(eventName, listener, options)
}
}, [eventName, element, options])
}
export { useEventListener }