Skip to content

Instantly share code, notes, and snippets.

@olavoasantos
Created November 15, 2019 22:26
Show Gist options
  • Save olavoasantos/d9b4046a7b31ba0ff1026d3cdf8b0d83 to your computer and use it in GitHub Desktop.
Save olavoasantos/d9b4046a7b31ba0ff1026d3cdf8b0d83 to your computer and use it in GitHub Desktop.
Skipable async function hook
/**
* useAsync<T>(asyncFunc, options)
* @arg asyncFunc: (variables: Options) => Promise<T> Asynchronous function to be called
* @arg options: Options: { Options. The hook will only call
* skip: boolean; the asyncFunc when `skip=false`.
* variables: { [key: string]: any } `variables` will be passed to the
* } function whe it's called.
*
* @returns [ executeFunction, asyncState ]
* ---
* Usage:
* const [skip, setSkip] = useState(true);
* const [execute, state] = useAsync(
* ({ name }) => new Promise((res, rej) => res(`Hello, ${name}`)),
* { skip, variables: { name: 'John' } },
* );
*
* // ...
*
* <button onClick={() => setSkip(false)}>Execute async function</button>
*/
import { useState, useEffect, useRef, useCallback } from 'react';
export type UseAsyncOptions = {
skip?: boolean;
variables?: {
[key: string]: any;
};
};
export type UseAsync<T = any> = {
loading: boolean;
data?: T;
error?: Error;
wasCalled: boolean;
};
const INITIAL_STATE = {
loading: false,
data: undefined,
error: undefined,
wasCalled: false,
};
const useAsync = <T = any>(
asyncFunc: (variable: UseAsyncOptions['variables']) => Promise<T>,
{ skip = false, variables = {} }: UseAsyncOptions = {},
): UseAsync<T> => {
/** Fetch state */
const [state, setState] = useState<UseAsync<T>>(INITIAL_STATE);
/** Was the call cancelled */
const cancelRef = useRef<boolean>(false);
const requestWasCancelled = useCallback(() => cancelRef.current, []);
/** Async function callback */
const cb = useCallback(() => {
/** We should only call it if we shouldn't skip it */
if (!skip && !state.wasCalled) {
setState({ loading: true, wasCalled: true });
asyncFunc(variables).then(
/** If success, put data on the state */
(data: T) =>
!requestWasCancelled() &&
setState({ data, loading: false, wasCalled: true }),
/** If error, put error on the state */
(error: Error) =>
!requestWasCancelled() &&
setState({ error, loading: false, wasCalled: true }),
);
}
}, [asyncFunc, requestWasCancelled, skip, state.wasCalled, variables]);
/** If the component is unmounted, cancel the call */
useEffect(() => {
cancelRef.current = false;
return () => {
cancelRef.current = true;
};
});
/** Call the function */
useEffect(() => {
cb();
}, [cb]);
/** Return the state */
return state;
};
export default useAsync;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment