import { DependencyList, useCallback, useRef, useState } from 'react';
import useMountedState from '../useMountedState';
export type PromiseType
> = P extends Promise
? T
: never;
export type FunctionReturningPromise = (...args: any[]) => Promise;
export type AsyncState =
| {
loading: boolean;
error?: undefined;
value?: undefined;
}
| {
loading: true;
error?: Error | undefined;
value?: T;
}
| {
loading: false;
error: Error;
value?: undefined;
}
| {
loading: false;
error?: undefined;
value: T;
};
type StateFromFunctionReturningPromise =
AsyncState>>;
export type AsyncFnReturn<
T extends FunctionReturningPromise = FunctionReturningPromise
> = [StateFromFunctionReturningPromise, T];
export default function useAsyncFn(
fn: T,
deps: DependencyList = [],
initialState: StateFromFunctionReturningPromise = { loading: false }
): AsyncFnReturn {
const lastCallId = useRef(0);
const isMounted = useMountedState();
const [state, set] =
useState>(initialState);
const callback = useCallback((...args: Parameters): ReturnType => {
const callId = ++lastCallId.current;
if (!state.loading) {
set(prevState => ({ ...prevState, loading: true }));
}
return fn(...args).then(
value => {
isMounted() &&
callId === lastCallId.current &&
set({ value, loading: false });
return value;
},
error => {
isMounted() &&
callId === lastCallId.current &&
set({ error, loading: false });
return error;
}
) as ReturnType;
}, deps);
return [state, callback as unknown as T];
}