All files / hooks index.js

100% Statements 31/31
100% Branches 12/12
100% Functions 13/13
100% Lines 28/28
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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114                10x                                         30x 30x 30x   30x   12x 5x 5x   7x       12x 12x 11x 1x 12x       30x 9x 9x     30x                                       17x 17x   17x   17x 5x     17x                                   2x     2x 1x       2x    
/**
 * @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]
}
 
/**
 * usePrevious Hook
 *
 * Got from https://usehooks.com/usePrevious/
 * It saves the previous value of props or state
 *
 * @param {*} value - The value to save
 *
 * @example
 * const [state, setState] = React.useState()
 * const prev = usePrevious(state)
 */
export function usePrevious(value) {
    // The ref object is a generic container whose current property is mutable ...
    // ... and can hold any value, similar to an instance property on a class
    const ref = useRef()
 
    // Store current value in ref
    useEffect(() => {
        ref.current = value
    }, [value]) // Only re-run if value changes
 
    // Return previous value (happens before update in useEffect above)
    return ref.current
}