| 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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94 |
37x
37x
37x
37x
12x
12x
7x
7x
5x
12x
12x
11x
1x
12x
37x
8x
1x
1x
37x
10x
10x
10x
37x
3x
3x
3x
3x
1x
3x
| import { useState, useCallback, useEffect, useRef } from "react"
/**
* usePromiseCallback Hook
*
* Hook dedicated to offer a sync way of accessing a promise state.
*
* Keeps the last result until new results are loaded, to avoid UI flashes.
*
* Supports a delayMS param to customise the loading threshold, to avoid UI flashes.
*
* Only processes the last promise call.
*
* @param {Promise} promise - The promise that would be wrapped
* @param {any[]} deps - extra deps used to re-create the callback
* @param {number} delayMS - The delay in ms to switch the loading state to true
*
* @example
* const [callback, result, loading, err] = usePromiseCallback(Promise.resolve, [], 500)
*/
export function usePromiseCallback(promise, deps = [], delayMS = 0) {
const [state, setState] = useState({})
const timeoutRef = useRef(0)
const callID = useRef(0)
const callback = useCallback((...fnArgs) => {
// set loading function helper
// keep the last result so we avoid UI flashes
const setLoading = state => ({ result: state.result, loading: true })
// if a delay is set, create a timeout to set the loading state
if (delayMS) {
clearTimeout(timeoutRef.current)
timeoutRef.current = setTimeout(() => setState(setLoading), delayMS)
} else {
setState(setLoading)
}
// only process if currentCallID is valid
const currentCallID = ++callID.current
return promise(...fnArgs)
.then(result => currentCallID === callID.current && setState({ result }))
.catch(err => currentCallID === callID.current && setState({ err }))
.finally(() => currentCallID === callID.current && clearTimeout(timeoutRef.current))
}, deps)
// clear timeout on umount and reset callID
useEffect(
() => () => {
callID.current = 0
clearTimeout(timeoutRef.current)
},
[]
)
// clear timeout on deps change
useEffect(() => {
callID.current++
clearTimeout(timeoutRef.current)
setState(({ result }) => ({ result }))
}, [...deps, delayMS])
return [callback, state.result, state.loading, state.err]
}
/**
* usePromise Hook
*
* Wrapped {@link module:hooks.usePromiseCallback|usePromiseCallback}.
*
* It gets triggered on init and every time the args change by default.
*
* Useful to fetch page data.
*
* @param {Promise} promise - The promise that would be wrapped
* @param {any[]} deps - deps to call the function with
* @param {number} delayMS - The delay in ms to switch the loading state to true
*
* @example
* const [callback, result, loading, err] = usePromise(Promise.resolve, ["hello"], 500)
*/
export function usePromise(promise, deps = [], delayMS = 0) {
const promiseCallback = usePromiseCallback(promise, deps, delayMS)
const [cb, ...props] = promiseCallback
const callback = useCallback(() => cb(...deps), [cb])
useEffect(() => {
callback()
}, [callback])
return [callback, ...props]
}
|