frontend/hooks/useEventListener.ts (view raw)
1import { RefObject, useEffect, useRef } from 'react'
2import useIsomorphicLayoutEffect from './useIsomorphicLayoutEffect'
3
4// Window Event based useEventListener interface
5function useEventListener<K extends keyof WindowEventMap>(
6 eventName: K,
7 handler: (event: WindowEventMap[K]) => void,
8 element?: undefined,
9 options?: boolean | AddEventListenerOptions,
10): void
11
12// Element Event based useEventListener interface
13function useEventListener<
14 K extends keyof HTMLElementEventMap,
15 T extends HTMLElement = HTMLDivElement,
16>(
17 eventName: K,
18 handler: (event: HTMLElementEventMap[K]) => void,
19 element: RefObject<T>,
20 options?: boolean | AddEventListenerOptions,
21): void
22
23// Document Event based useEventListener interface
24function useEventListener<K extends keyof DocumentEventMap>(
25 eventName: K,
26 handler: (event: DocumentEventMap[K]) => void,
27 element: RefObject<Document>,
28 options?: boolean | AddEventListenerOptions,
29): void
30
31function useEventListener<
32 KW extends keyof WindowEventMap,
33 KH extends keyof HTMLElementEventMap,
34 T extends HTMLElement | void = void,
35>(
36 eventName: KW | KH,
37 handler: (
38 event: WindowEventMap[KW] | HTMLElementEventMap[KH] | Event,
39 ) => void,
40 element?: RefObject<T>,
41 options?: boolean | AddEventListenerOptions,
42) {
43 // Create a ref that stores handler
44 const savedHandler = useRef(handler)
45
46 useIsomorphicLayoutEffect(() => {
47 savedHandler.current = handler
48 }, [handler])
49
50 useEffect(() => {
51 // Define the listening target
52 const targetElement: T | Window = element?.current || window
53 if (!(targetElement && targetElement.addEventListener)) {
54 return
55 }
56
57 // Create event listener that calls handler function stored in ref
58 const eventListener: typeof handler = event => savedHandler.current(event)
59
60 targetElement.addEventListener(eventName, eventListener, options)
61
62 // Remove event listener on cleanup
63 return () => {
64 targetElement.removeEventListener(eventName, eventListener)
65 }
66 }, [eventName, element, options])
67}
68
69export default useEventListener