/**
* Copyright (c) 2018 - present Zilliqa Research Pte. Ltd.
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
import { useEffect, useReducer, useRef, useCallback } from 'react';
export enum statusTypes {
Initial = 'Initial',
Pending = 'Pending',
Fulfilled = 'Fulfilled',
Rejected = 'Rejected',
}
enum actionTypes {
Init = 'Init',
Run = 'Run',
Abort = 'Abort',
Fulfill = 'Fulfill',
Reject = 'Reject',
}
interface Args {
type: actionTypes;
payload: any;
}
const getStatusProps = (status: statusTypes) => ({
status,
isInitial: status === statusTypes.Initial,
isPending: status === statusTypes.Pending,
isFulfilled: status === statusTypes.Fulfilled,
isRejected: status === statusTypes.Rejected,
});
const initialize = () => ({
data: undefined,
error: undefined,
...getStatusProps(statusTypes.Initial),
});
const reducer = (state: any, args: Args) => {
const { type, payload } = args;
switch (type) {
case actionTypes.Init:
return initialize();
case actionTypes.Run:
return {
...state,
data: undefined,
error: undefined,
...getStatusProps(statusTypes.Pending),
};
case actionTypes.Fulfill:
return {
...state,
data: payload,
error: undefined,
...getStatusProps(statusTypes.Fulfilled),
};
case actionTypes.Reject:
return {
...state,
data: undefined,
error: payload,
...getStatusProps(statusTypes.Rejected),
};
case actionTypes.Abort:
return {
data: undefined,
error: payload,
...getStatusProps(statusTypes.Rejected),
};
}
};
export interface UseAsyncFn {
(...args: any[]): any;
}
export const useAsyncFn: UseAsyncFn = ({ fn, deferred = false, ...args }) => {
const [state, dispatch] = useReducer(reducer, initialize());
const asyncFn = fn;
const isMountedRef = useRef(true);
const abortControllerRef = useRef();
const argsRef = useRef(args);
const init = (): void => {
isMountedRef.current && dispatch({ type: actionTypes.Init, payload: undefined });
};
const run = useCallback(
async (newArgs?: any) => {
if (newArgs) {
argsRef.current = newArgs;
}
abortControllerRef.current = new AbortController();
isMountedRef.current && dispatch({ type: actionTypes.Run, payload: undefined });
try {
const signal: AbortSignal | undefined =
abortControllerRef.current && abortControllerRef.current.signal;
const payload = await asyncFn({ args: argsRef.current, signal });
isMountedRef.current && dispatch({ type: actionTypes.Fulfill, payload });
} catch (error: any) {
const curType =
error && error.name === 'AbortError' ? actionTypes.Abort : actionTypes.Reject;
isMountedRef.current && dispatch({ type: curType, payload: error });
}
},
[asyncFn]
);
const abort = (): void => {
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
};
useEffect(
() => () => {
isMountedRef.current = false;
},
[]
);
useEffect(() => {
if (!deferred) {
run();
}
return abort;
}, [fn, run]);
return { ...state, run, init, abort };
};