1 | import { useMemo, useRef } from 'react';
|
2 | import useMounted from './useMounted';
|
3 | import useWillUnmount from './useWillUnmount';
|
4 |
|
5 | /*
|
6 | * Browsers including Internet Explorer, Chrome, Safari, and Firefox store the
|
7 | * delay as a 32-bit signed integer internally. This causes an integer overflow
|
8 | * when using delays larger than 2,147,483,647 ms (about 24.8 days),
|
9 | * resulting in the timeout being executed immediately.
|
10 | *
|
11 | * via: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout
|
12 | */
|
13 | const MAX_DELAY_MS = 2 ** 31 - 1;
|
14 | function setChainedTimeout(handleRef, fn, timeoutAtMs) {
|
15 | const delayMs = timeoutAtMs - Date.now();
|
16 | handleRef.current = delayMs <= MAX_DELAY_MS ? setTimeout(fn, delayMs) : setTimeout(() => setChainedTimeout(handleRef, fn, timeoutAtMs), MAX_DELAY_MS);
|
17 | }
|
18 |
|
19 | /**
|
20 | * Returns a controller object for setting a timeout that is properly cleaned up
|
21 | * once the component unmounts. New timeouts cancel and replace existing ones.
|
22 | *
|
23 | *
|
24 | *
|
25 | * ```tsx
|
26 | * const { set, clear } = useTimeout();
|
27 | * const [hello, showHello] = useState(false);
|
28 | * //Display hello after 5 seconds
|
29 | * set(() => showHello(true), 5000);
|
30 | * return (
|
31 | * <div className="App">
|
32 | * {hello ? <h3>Hello</h3> : null}
|
33 | * </div>
|
34 | * );
|
35 | * ```
|
36 | */
|
37 | export default function useTimeout() {
|
38 | const isMounted = useMounted();
|
39 |
|
40 | // types are confused between node and web here IDK
|
41 | const handleRef = useRef();
|
42 | useWillUnmount(() => clearTimeout(handleRef.current));
|
43 | return useMemo(() => {
|
44 | const clear = () => clearTimeout(handleRef.current);
|
45 | function set(fn, delayMs = 0) {
|
46 | if (!isMounted()) return;
|
47 | clear();
|
48 | if (delayMs <= MAX_DELAY_MS) {
|
49 | // For simplicity, if the timeout is short, just set a normal timeout.
|
50 | handleRef.current = setTimeout(fn, delayMs);
|
51 | } else {
|
52 | setChainedTimeout(handleRef, fn, Date.now() + delayMs);
|
53 | }
|
54 | }
|
55 | return {
|
56 | set,
|
57 | clear,
|
58 | handleRef
|
59 | };
|
60 | }, []);
|
61 | } |
\ | No newline at end of file |