Skip to content

Instantly share code, notes, and snippets.

@dfee
Created February 22, 2019 23:12
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dfee/0686746c4b37ad45ba88b20a66a60432 to your computer and use it in GitHub Desktop.
Save dfee/0686746c4b37ad45ba88b20a66a60432 to your computer and use it in GitHub Desktop.
import { useState, useEffect } from "react";
export type AsyncState<T> =
| { loading: false; error?: undefined; value?: undefined }
| { loading: true; error?: undefined; value?: undefined }
| { loading: false; error: Error; value?: undefined }
| { loading: false; error?: undefined; value: T };
export type AsyncUtils<C extends {}, A extends any[]> = {
setArgs: (args: A | undefined) => void;
setContext: (optiosn: C | undefined) => void;
};
const noop = () => {};
/**
* Executes an async function (a function that returns a promise), and
* returns the result (value or error).
*
* @param fn the async function to execute
* @param options a bag of options controlling the async execution
*
* @param options.initialContext context necessary for initial execution
* @param options.initialArgs args for initial execution (initial execution is skipped if undefined)
* @param options.onSuccess callback receiving the context and value, executed upon `then`
* @param options.onError callback receiving the context and reason, executed upon `catch`
* @param options.onFinally callback receiving the context, executed upon `finally`
*
* Returns an array that is a state object and a utils object `[state, utils]`
* @param state.loading boolean indicating whether an async action is in flight
* @param state.error the error or reason thrown during async execution
* @param state.value the value returned from async execution
* @param utils.setArguments set the argumetns for async execution (if the value is undefined, no execution occurs)
* @param utils.setContext set any context necessary for async execution
*/
export const useAsync = <
C extends {} = any,
A extends any[] = any,
T extends {} = any
>(
fn: (...args: A) => Promise<T>,
options?: {
initialContext?: C;
initialArgs?: A;
onSuccess?: ({ context, value }: { context?: C; value: T }) => void;
onError?: ({ context, reason }: { context?: C; reason: any }) => void;
onFinally?: ({ context }: { context?: C }) => void;
},
): [AsyncState<T>, AsyncUtils<C, A>] => {
const {
initialArgs = undefined,
initialContext = undefined,
onSuccess = noop,
onError = noop,
onFinally = noop,
} = { ...options };
let mounted = true;
const [state, setState] = useState<AsyncState<T>>({ loading: false });
const [args, setArgs] = useState<A | undefined>(initialArgs);
const [context, setContext] = useState<C | undefined>(initialContext);
const utils = { setArgs, setContext };
useEffect(() => {
if (args === undefined) {
setState({ loading: false });
} else {
setState({ loading: true });
fn(...args)
.then(value => {
if (mounted) {
setState({ loading: false, value });
}
onSuccess({ context, value });
})
.catch(reason => {
if (mounted) {
setState({ loading: false, error: reason });
}
onError({ context, reason });
})
.finally(() => {
onFinally({ context });
});
}
return () => {
mounted = false;
};
}, [args, context]);
return [state, utils];
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment