Skip to content

Instantly share code, notes, and snippets.

@olavoasantos
Created November 15, 2019 22:22
Show Gist options
  • Save olavoasantos/7284911c86f8dd9c71edf8f270a79bc9 to your computer and use it in GitHub Desktop.
Save olavoasantos/7284911c86f8dd9c71edf8f270a79bc9 to your computer and use it in GitHub Desktop.
Lazily run async functions with React hooks
/**
* useLazyAsync<T>(asyncFunc, options)
* @arg asyncFunc: (variables: Options) => Promise<T> Asynchronous function to be called
* @arg options: Options: {variables: { [key: string]: any }} Options. `variables` will be passed
* to the function whe it's called
* @returns [ executeFunction, asyncState ]
* ---
* Usage:
* const [execute, state] = useLazyAsync(
* ({ name }) => new Promise((res, rej) => res(`Hello, ${name}`)),
* { variables: { name: 'John' } },
* );
*
* // ...
*
* <button onClick={() => execute()}>Execute async function</button>
*/
import { useState, useEffect, useRef, useCallback } from 'react';
export type UseLazyAsyncOptions = {
variables?: {
[key: string]: any;
};
};
export type UseLazyAsync<T = any> = [(() => void), AsyncState<T>];
export type AsyncState<T> = {
loading: boolean;
data?: T;
error?: Error;
wasCalled: boolean;
};
const INITIAL_STATE = {
loading: false,
data: undefined,
error: undefined,
wasCalled: false,
};
const useLazyAsync = <T = any>(
asyncFunc: (variables: UseLazyAsyncOptions['variables']) => Promise<T>,
{ variables = {} }: UseLazyAsyncOptions = {},
): UseLazyAsync<T> => {
/** Fetch state */
const [state, setState] = useState<AsyncState<T>>(INITIAL_STATE);
const [shouldCall, setCall] = useState<boolean>(false);
const execute = useCallback<() => void>(() => {
setCall(true);
}, []);
/** 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 (shouldCall && !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, state.wasCalled, variables, shouldCall]);
/** 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 [execute, state];
};
export default useLazyAsync;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment