-
Notifications
You must be signed in to change notification settings - Fork 95
/
index.ts
65 lines (55 loc) · 2.04 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import {type RefObject, useEffect, useMemo} from 'react';
import {useIsMounted} from '../useIsMounted/index.js';
import {useSyncedRef} from '../useSyncedRef/index.js';
import {hasOwnProperty, off, on} from '../util/misc.js';
/**
* An HTML element or ref object containing an HTML element.
*
* @param target An HTML element or ref object containing an HTML element.
* @param params Parameters specific to the target element's `addEventListener` method. Commonly
* something like `[eventName, listener, options]`.
*/
export function useEventListener<T extends EventTarget>(
target: RefObject<T> | T | null,
...params:
| Parameters<T['addEventListener']>
| [string, EventListenerOrEventListenerObject | ((...args: any[]) => any), ...any]
): void {
const isMounted = useIsMounted();
// Create static event listener
const listenerRef = useSyncedRef(params[1]);
const eventListener = useMemo<EventListener>(
() =>
// As some event listeners designed to be used through `this`
// it is better to make listener a conventional function as it
// infers call context
function (this: T, ...args) {
if (!isMounted()) {
return;
}
// We dont care if non-listener provided, simply dont do anything
if (typeof listenerRef.current === 'function') {
listenerRef.current.apply(this, args);
} else if (typeof listenerRef.current!.handleEvent === 'function') {
listenerRef.current!.handleEvent.apply(this, args);
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
);
useEffect(() => {
const tgt = isRefObject(target) ? target.current : target;
if (!tgt) {
return;
}
const restParams: unknown[] = params.slice(2);
on(tgt, params[0], eventListener, ...restParams);
return () => {
off(tgt, params[0], eventListener, ...restParams);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [target, params[0]]);
}
function isRefObject<T>(target: RefObject<T> | T | null): target is RefObject<T> {
return target !== null && typeof target === 'object' && hasOwnProperty(target, 'current');
}