import {Dispatch, SetStateAction, useRef, useState} from 'react' export type Func = (...args: any[]) => R export type Fetch>>> = (p?: {force?: boolean, clean?: boolean}, ..._: Parameters) => ReturnType; export interface FetchParams { force?: boolean, clean?: boolean } type ThenArg = T extends PromiseLike ? U : T type FetcherResult = ThenArg> export type UseFetcher>>, E = any> = { entity?: FetcherResult, loading: boolean, error?: E fetch: Fetch, setEntity: Dispatch | undefined>>, clearCache: () => void, }; /** * Factorize fetching logic which goal is to prevent unneeded fetchs and expose loading indicator + error status. */ export const useFetcher = >, E = any>( fetcher: F, initialValue?: FetcherResult, mapError: (_: any) => E = _ => _ ): UseFetcher => { const [entity, setEntity] = useState | undefined>(initialValue) const [error, setError] = useState() const [loading, setLoading] = useState(false) const fetch$ = useRef>>() const fetch = ({force = true, clean = true}: FetchParams = {}, ...args: any[]): Promise> => { if(!force) { if (fetch$.current) { return fetch$.current! } if (entity) { return Promise.resolve(entity) } } if (clean) { setError(undefined) setEntity(undefined) } setLoading(true) fetch$.current = fetcher(...args) fetch$.current .then((x: FetcherResult) => { setLoading(false) setEntity(x) fetch$.current = undefined }) .catch((e) => { setLoading(false) fetch$.current = undefined setError(mapError(e)) setEntity(undefined) // return Promise.reject(e) throw e }) return fetch$.current } const clearCache = () => { setEntity(undefined) setError(undefined) fetch$.current = undefined } return { entity, loading, error, // TODO(Alex) not sure the error is legitimate fetch: fetch as any, setEntity, clearCache } }