Created
November 15, 2019 22:22
-
-
Save olavoasantos/7284911c86f8dd9c71edf8f270a79bc9 to your computer and use it in GitHub Desktop.
Lazily run async functions with React hooks
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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