You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tailchat/shared/hooks/useAsyncFn.ts

70 lines
1.8 KiB
TypeScript

import { DependencyList, useCallback, useRef, useState } from 'react';
import type { FunctionReturningPromise, PromiseType } from '../types';
import { useMountedState } from './useMountedState';
// Reference: https://github.com/streamich/react-use/blob/master/src/useAsyncFn.ts
export type AsyncState<T> =
| {
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<T extends FunctionReturningPromise> =
AsyncState<PromiseType<ReturnType<T>>>;
export type AsyncFnReturn<
T extends FunctionReturningPromise = FunctionReturningPromise
> = [StateFromFunctionReturningPromise<T>, T];
export function useAsyncFn<T extends FunctionReturningPromise>(
fn: T,
deps: DependencyList = [],
initialState: StateFromFunctionReturningPromise<T> = { loading: false }
): AsyncFnReturn<T> {
const lastCallId = useRef(0);
const isMounted = useMountedState();
const [state, set] =
useState<StateFromFunctionReturningPromise<T>>(initialState);
const callback = useCallback((...args: Parameters<T>): ReturnType<T> => {
const callId = ++lastCallId.current;
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<T>;
}, deps);
return [state, callback as unknown as T];
}