/**
* @module hooks
*/
import { useState, useCallback, useEffect, useRef } from "react"
// set loading function helper
// keep the last result so we avoid UI flashes
const setLoading = state => ({ result: state.result, loading: true })
/**
* 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) => {
// 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
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
useEffect(() => {
callID.current = 0
clearTimeout(timeoutRef.current)
}, [])
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(...deps)
}, [callback])
return [callback, ...props]
}
|