• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

alessiofrittoli / react-hooks / 14958971000

11 May 2025 07:22PM UTC coverage: 100.0%. First build
14958971000

push

github

alessiofrittoli
tests: improved unit tests

122 of 122 branches covered (100.0%)

Branch coverage included in aggregate %.

249 of 249 relevant lines covered (100.0%)

10.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/src/browser-api/useEventListener.ts
1
import { useEffect } from 'react'
3✔
2

3
type CommonEventHandler<
4
        W extends keyof WindowEventMap,
5
        D extends keyof DocumentEventMap,
6
        E extends keyof HTMLElementEventMap,
7
        M extends keyof MediaQueryListEventMap,
8
        C extends keyof Record<string, Event>,
9
> = ( event: (
10
        | WindowEventMap[ W ]
11
        | DocumentEventMap[ D ]
12
        | HTMLElementEventMap[ E ]
13
        | MediaQueryListEventMap[ M ]
14
        | Record<string, Event>[ C ]
15
        | Event
16
) ) => void
17

18

19
/**
20
 * Specifies characteristics about the event listener.
21
 * 
22
 * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#options)
23
 */
24
export interface AddEventListenerOptions
25
{
26
        /**
27
         * A boolean value indicating that events of this type will be dispatched to the registered
28
         * listener before being dispatched to any `EventTarget`beneath it in the DOM tree.
29
         * 
30
         * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#capture) 
31
         * 
32
         * @default false
33
         */
34
        capture?: boolean
35
        /**
36
         * A boolean value indicating that the listener should be invoked at most once after being added.
37
         * If `true`, the listener would be automatically removed when invoked.
38
         * 
39
         * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#once) 
40
         * 
41
         * @default false
42
         */
43
    once?: boolean
44
        /**
45
         * A boolean value that, if `true`, indicates that the function specified by `listener`
46
         * will never call [`preventDefault()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault).
47
         * If a passive listener calls `preventDefault()`, nothing will happen and a console warning may be generated.
48
         * 
49
         * If this option is not specified it defaults to `false` – except that in browsers other than Safari,
50
         * it defaults to `true` for [`wheel`](https://developer.mozilla.org/en-US/docs/Web/API/Element/wheel_event),
51
         * [`mousewheel`](https://developer.mozilla.org/en-US/docs/Web/API/Element/mousewheel_event),
52
         * [`touchstart`](https://developer.mozilla.org/en-US/docs/Web/API/Element/touchstart_event) and
53
         * [`touchmove`](https://developer.mozilla.org/en-US/docs/Web/API/Element/touchmove_event) events.
54
         * 
55
         * See [Using passive listeners](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#using_passive_listeners) to learn more.
56
         * 
57
         * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#passive) 
58
         */
59
    passive?: boolean
60
        /**
61
         * An [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal).
62
         * The listener will be removed when the
63
         * [`abort()`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort) method
64
         * of the [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) which
65
         * owns the `AbortSignal` is called. If not specified, no `AbortSignal` is associated with the listener.
66
         * 
67
         * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#signal) 
68
         */
69
    signal?: AbortSignal
70
}
71

72

73
/**
74
 * The `addEventListener` options.
75
 * 
76
 * - If `boolean` is given, indicates whether events of this type will be dispatched to the registered
77
 * listener before being dispatched to any EventTarget beneath it in the DOM tree. See [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#usecapture).
78
 * - If an object is given, it specifies characteristics about the event listener. See [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#options).
79
 */
80
export type ListenerOptions = boolean | AddEventListenerOptions
81

82

83
/**
84
 * The Window Event listener.
85
 * 
86
 * @param event The event object that implements the [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event) interface.
87
 */
88
export type WindowEventListener<
89
        K extends keyof WindowEventMap
90
> = ( event: WindowEventMap[ K ] ) => void
91

92

93
/**
94
 * The Document Event listener.
95
 * 
96
 * @param event The event object that implements the [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event) interface.
97
 */
98
export type DocumentEventListener<
99
        K extends keyof DocumentEventMap
100
> = ( event: DocumentEventMap[ K ] ) => void
101

102

103
/**
104
 * The MediaQueryList Event listener.
105
 * 
106
 * @param event The [`MediaQueryListEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryListEvent) object.
107
 */
108
export type MediaQueryEventListener<
109
        K extends keyof MediaQueryListEventMap
110
> = ( event: MediaQueryListEventMap[ K ] ) => void
111

112

113
/**
114
 * The MediaQueryList "change" Event listener.
115
 * 
116
 * @param event The [`MediaQueryListEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryListEvent) object.
117
 */
118
export type MediaQueryChangeListener = ( event: MediaQueryListEventMap[ 'change' ] ) => void
119

120

121
/**
122
 * The HTMLElement Event listener.
123
 * 
124
 * @param event The event object that implements the [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event) interface.
125
 */
126
export type ElementEventListener<
127
        K extends keyof HTMLElementEventMap
128
> = ( event: HTMLElementEventMap[ K ] ) => void
129

130

131
export interface CommonListenerOptions
132
{
133
        /**
134
         * A custom callback executed before event listener get attached.
135
         * 
136
         */
137
        onLoad?: () => void
138
        /**
139
         * A custom callback executed after event listener get removed.
140
         * 
141
         */
142
        onCleanUp?: () => void
143
        /**
144
         * Specifies characteristics about the event listener.
145
         * 
146
         * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#options)
147
         */
148
        options?: ListenerOptions,
149
}
150

151

152
export interface WindowListenerOptions<
153
        K extends keyof WindowEventMap
154
> extends CommonListenerOptions
155
{
156
        /**
157
         * The Window Event listener.
158
         * 
159
         * @param event The event object that implements the [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event) interface.
160
         */
161
        listener: WindowEventListener<K>,
162
}
163

164

165
export interface DocumentListenerOptions<
166
        K extends keyof DocumentEventMap
167
> extends CommonListenerOptions
168
{
169
        /**
170
         * The `Document` reference or a React RefObject of the `Document`.
171
         * 
172
         */
173
        target: Document | null | React.RefObject<Document | null>,
174
        /**
175
         * The Document Event listener.
176
         * 
177
         * @param event The event object that implements the [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event) interface.
178
         */
179
        listener: DocumentEventListener<K>,
180
}
181

182

183
export interface MediaQueryListenerOptions extends CommonListenerOptions
184
{
185
        /**
186
         * The Media Query string to check.
187
         * 
188
         */
189
        query: string,
190
        /**
191
         * The MediaQueryList Event listener.
192
         * 
193
         * @param event The [`MediaQueryListEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryListEvent) object.
194
         */
195
        listener: MediaQueryChangeListener,
196
}
197

198

199
export interface ElementListenerOptions<
200
        T extends HTMLElement,
201
        K extends keyof HTMLElementEventMap,
202
> extends CommonListenerOptions
203
{
204
        /**
205
         * The React RefObject of the target where the listener get attached to.
206
         * 
207
         */
208
        target: T | React.RefObject<T | null>,
209
        /**
210
         * The HTMLElement Event listener.
211
         * 
212
         * @param event The event object that implements the [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event) interface.
213
         */
214
        listener: ElementEventListener<K>,
215
}
216

217

218
export interface CustomEventListenerOptions<
219
        T extends Record<string, Event>,
220
        K extends keyof T = keyof T,
221
> extends CommonListenerOptions
222
{
223
        /**
224
         * The target where the listener get attached to.
225
         * 
226
         * If not set, the listener will get attached to the `Window` object.
227
         */
228
        target?: Document | HTMLElement | null | React.RefObject<Document | HTMLElement | null>,
229
        /**
230
         * The Event listener.
231
         * 
232
         * @param event The event object that implements the [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event) interface.
233
         */
234
        listener: ( event: T[ K ] ) => void
235
}
236

237

238
/**
239
 * Attach a new Event listener to the `Window` object.
240
 * 
241
 * @param type                The `Window` event name or an array of event names.
242
 * @param options        An object defining init options. See {@link WindowListenerOptions} for more info.
243
 * 
244
 * @example
245
 * 
246
 * #### Add a new `Window` event listener
247
 * 
248
 * ```ts
249
 * useEventListener( 'scroll', {
250
 *         listener: useCallback( () => {
251
 *                 console.log( window.scrollY )
252
 *         }, [] )
253
 * } )
254
 * ```
255
 * 
256
 * ---
257
 * 
258
 * #### Add multiple `Window` event listeners
259
 * 
260
 * ```ts
261
 * useEventListener( [ 'scroll', 'resize' ], {
262
 *         listener: useCallback( event => {
263
 *                 console.log( event )
264
 *         }, [] )
265
 * } )
266
 * ```
267
 */
268
export function useEventListener<K extends keyof WindowEventMap>(
269
        type        : K | K[],
270
        options        : WindowListenerOptions<K>
271
): void
272

273

274
/**
275
 * Attach a new Event listener to the `Document` object.
276
 * 
277
 * @param type                The `Document` event name or an array of event names.
278
 * @param options        An object defining init options. See {@link DocumentListenerOptions} for more info.
279
 * 
280
 * @example
281
 * 
282
 * #### Add a new `Document` event listener
283
 * 
284
 * ```ts
285
 * // A React.RefObject<Document | null> can be used too.
286
 * const documentRef = useRef( typeof document !== 'undefined' ? document : null )
287
 * 
288
 * useEventListener( 'visibilitychange', {
289
 *         target  : typeof document !== 'undefined' ? document : null, // or `documentRef`
290
 *         listener: useCallback( () => {
291
 *                 console.log( document.visibilityState )
292
 *         }, [] )
293
 * } )
294
 * ```
295
 * 
296
 * ---
297
 * 
298
 * #### Add multiple `Document` event listeners
299
 * 
300
 * ```ts
301
 * useEventListener( [ 'visibilitychange', 'dblclick' ], {
302
 *         target  : typeof document !== 'undefined' ? document : null,
303
 *         listener: useCallback( ( event ) => {
304
 *                 console.log( event )
305
 *         }, [] )
306
 * } )
307
 * ```
308
 */
309
export function useEventListener<K extends keyof DocumentEventMap>(
310
        type        : K | K[],
311
        options        : DocumentListenerOptions<K>
312
): void
313

314

315
/**
316
 * Attach a new Event listener to a `HTMLElement` object.
317
 * 
318
 * @param type                The `HTMLElement` event name or an array of event names.
319
 * @param options        An object defining init options. See {@link ElementListenerOptions} for more info.
320
 * 
321
 * @example
322
 * 
323
 * #### Add a new `HTMLElement` event listener
324
 * 
325
 * ```tsx
326
 * 'use client'
327
 * 
328
 * const Component: React.FC = () => {
329
 * 
330
 *         const elementRef = useRef<HTMLButtonElement>( null )
331
 * 
332
 *         useEventListener( 'click', {
333
 *                 target  : elementRef,
334
 *                 listener: useCallback( event => {
335
 *                         console.log( event )
336
 *                 }, [] )
337
 *         } )
338
 * 
339
 *         return (
340
 *                 <button ref={ elementRef }>Click me</button>
341
 *         )
342
 * 
343
 * }
344
 * ```
345
 * 
346
 * ---
347
 * 
348
 * #### Add multiple `HTMLElement` event listeners
349
 * 
350
 * ```tsx
351
 * 'use client'
352
 * 
353
 * const Component: React.FC = () => {
354
 * 
355
 *         const elementRef = useRef<HTMLButtonElement>( null )
356
 * 
357
 *         useEventListener( [ 'click', 'touchmove' ], {
358
 *                 target  : elementRef,
359
 *                 listener: useCallback( event => {
360
 *                         console.log( event )
361
 *                 }, [] )
362
 *         } )
363
 * 
364
 *         return (
365
 *                 <button ref={ elementRef }>Click me</button>
366
 *         )
367
 * 
368
 * }
369
 * ```
370
 */
371
export function useEventListener<
372
        T extends HTMLElement,
373
        K extends keyof HTMLElementEventMap,
374
>(
375
        type        : K | K[],
376
        options        : ElementListenerOptions<T, K>
377
): void
378

379

380
/**
381
 * Listen MediaQuery changes.
382
 * 
383
 * @param type                The `MediaQueryList` event name or an array of event names.
384
 * @param options        An object defining init options. See {@link MediaQueryListenerOptions} for more info.
385
 * 
386
 * @example
387
 * 
388
 * #### Listen for MediaQuery changes.
389
 * 
390
 * ```ts
391
 * useEventListener( 'change', {
392
 *         query   : '(max-width: 768px)',
393
 *         listener: useCallback( event => {
394
 *                 console.error( event.matches )
395
 *         }, [] ),
396
 * } )
397
 * ```
398
 */
399
export function useEventListener(
400
        type        : 'change',
401
        options        : MediaQueryListenerOptions
402
): void
403

404

405
/**
406
 * Attach a new custom Event listener to the given target.
407
 * 
408
 * @param type                The custom event name or an array of event names.
409
 * @param options        An object defining init options. See {@link CustomEventListenerOptions} for more info.
410
 * 
411
 * @example
412
 * 
413
 * #### Add a new custom event listener
414
 * 
415
 * ```tsx
416
 * 'use client'
417
 * 
418
 * const Component: React.FC = () => {
419
 * 
420
 *         const elementRef = useRef<HTMLButtonElement>( null )
421
 * 
422
 *         useEventListener( 'customevent', {
423
 *                 target  : elementRef, // could be document. window will be used if omitted.
424
 *                 listener: useCallback( event => {
425
 *                         console.log( event )
426
 *                 }, [] )
427
 *         } )
428
 * 
429
 *         useEffect( () => {
430
 *                 elementRef.current?.dispatchEvent( new Event( 'customevent' ) )
431
 *         }, [] )
432
 * 
433
 *         return (
434
 *                 <button ref={ elementRef }>Button</button>
435
 *         )
436
 * 
437
 * }
438
 * ```
439
 * 
440
 * ---
441
 * 
442
 * #### Adding types for custom events
443
 * 
444
 * ```tsx
445
 * interface Details
446
 * {
447
 *         property?: string
448
 * }
449
 * 
450
 * type CustomEventMap = {
451
 *         customevent                : CustomEvent<Details>
452
 *         anothercustomEvent        : MouseEvent
453
 * }
454
 * 
455
 * type CustomEventType = keyof CustomEventMap
456
 * const elementRef = useRef<HTMLButtonElement>( null )
457
 * const eventType: CustomEventType = 'anothercustomEvent'
458
 * 
459
 * useEventListener<CustomEventMap, typeof eventType>( eventType, {
460
 *         target  : elementRef,
461
 *         listener: useCallback( event => {
462
 *                 console.log( event )
463
 *         }, [] )
464
 * } )
465
 * 
466
 * useEffect( () => {
467
 * 
468
 *         elementRef.current?.dispatchEvent( new CustomEvent<Details>( 'customevent', {
469
 *                 detail: {
470
 *                         property: 'value'
471
 *                 }
472
 *         } ) )
473
 * 
474
 * }, [] ) 
475
 *```
476
 */
477
export function useEventListener<
478
        T extends Record<string, Event>,
479
        K extends keyof T = keyof T,
480
>(
481
        type        : K | K[],
482
        options        : CustomEventListenerOptions<T, K>
483
): void
484

485

486
/**
487
 * Attach a new Event listener to the `Window`, `Document`, `MediaQueryList` or an `HTMLElement`.
488
 * 
489
 * @param type                The event name or an array of event names.
490
 * @param options        An object defining init options.
491
 */
492
export function useEventListener<
3✔
493
        W extends keyof WindowEventMap,
494
        D extends keyof DocumentEventMap,
495
        E extends keyof HTMLElementEventMap,
496
        M extends keyof MediaQueryListEventMap,
497
        C extends keyof Record<string, Event>,
498
>(
499
        type: W | D | E | M | C | ( W | D | E | M | C )[],
500
        options: {
501
                listener: CommonEventHandler<W, D, E, M, C>,
502
                target?        : Document | HTMLElement | null | React.RefObject<Document | HTMLElement | null>,
503
                query?        : string,
504
        } & CommonListenerOptions
505
)
506
{
507
        const {
508
                target, query, options: listenerOptions,
509
                listener, onLoad, onCleanUp,
510
        } = options
14✔
511

512
        useEffect( () => {
14✔
513

514
                /**
515
                 * Defines the event types.
516
                 * 
517
                 */
518
                const types = Array.isArray( type ) ? type : [ type ]
14✔
519

520
                /**
521
                 * Defines the target where listeners get attached to.
522
                 * 
523
                 */
524
                const targetElement = (
14✔
525
                        query
14✔
526
                                ? window.matchMedia( query )
527
                                : (
528
                                        target && 'current' in target
33✔
529
                                                ? target.current
530
                                                : target
531
                                )
532
                ) ?? window
533

534
                if ( ! targetElement.addEventListener ) {
14✔
535
                        return
1✔
536
                }
537

538
                onLoad?.()
13✔
539

540
                types.map( type => {
13✔
541
                        targetElement.addEventListener( type, listener, listenerOptions )
14✔
542
                } )
543

544

545
                return () => {
13✔
546
                        types.map( type => {
13✔
547
                                targetElement.removeEventListener( type, listener, listenerOptions )
14✔
548
                        } )
549
                        onCleanUp?.()
13✔
550
                }
551

552
        }, [
553
                type, target, query, listenerOptions,
554
                listener, onLoad, onCleanUp
555
        ] )
556

557
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc