Created
November 15, 2019 22:26
-
-
Save olavoasantos/d9b4046a7b31ba0ff1026d3cdf8b0d83 to your computer and use it in GitHub Desktop.
Skipable async function hook
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
/** | |
* 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