import { useCallback, useEffect, useMemo, useState } from "react" type AsyncCallbackOutput = [ (...args: TArgs) => Promise, { loading: boolean; error?: any; result?: TResult; args?: TArgs; } ]; type AsyncOutput = [ TResult | undefined, { loading: boolean; error?: any; rerun: () => Promise; } ] const useAsyncCallback = < TArgs extends any[], TResult, >(fn: (...args: TArgs) => Promise, deps: any[]): AsyncCallbackOutput => { const [result, setResult] = useState(); const [prevArgs, setPrevArgs] = useState(); const [loading, setLoading] = useState(false); const [error, setError] = useState(); const action = useCallback(fn, deps); const invoke = useCallback( async (...args: TArgs) => { setLoading(true); setError(false); setPrevArgs(args); try { const output = await action(...args); setResult(output); return output; } catch (err) { setResult(undefined); setError(err); throw err; } finally { setLoading(false); } }, [setLoading, setError, setResult, action, ...deps], ); const options = useMemo( () => { const output: AsyncCallbackOutput = [ invoke, { result, loading, error, args: prevArgs, } ]; return output; }, [invoke, result, loading, error, prevArgs, ...deps], ); return options; }; const useAsync = (fn: () => Promise, deps: any[]): AsyncOutput => { const [invoke, options] = useAsyncCallback(fn, deps); useEffect( () => { invoke(); }, [invoke], ); const localOptions = useMemo( () => ({ loading: options.loading, error: options.error, rerun: invoke, }), [invoke, options.loading, options.error], ); return [ options.result, localOptions, ] }; export type { AsyncCallbackOutput }; export { useAsync, useAsyncCallback };