useAsyncFunc: React hook for any async function
Kevin White
Posted on May 13, 2021
Source code and demo for generic async operation React hook. Live example.
Want to suggest an improvement? I'm all ears! Please file an issue or open a PR!
Usage
import useAsyncFunc from "useAsyncFunc";
const myAsyncFunc = (arg1, arg2) => {
// do work
};
const App = () => {
const [{ isLoading, isError, data }, setArgs] = useAsyncFn(myAsyncFunc, ['firstArg', 'secondArg']);
return (
{data && <div>{data}</div>}
{isLoading && <LoadingIcon/>}
{isError && <ErrorMsg/>}
);
};
Overview
This useAsyncFunc
React hook stands on the shoulders of giants. Robin Wieruch's excellent How to fetch data with React Hooks walks you step-by-step through the creation of a robust useDataApi
hook, which provides an exhaustively capable data fetching hook.
The purpose of this post is to further build on that work by genericizing it to run any async function (and not just fetch(myUrl)
).
useAsyncFunc
/**
* @param {function} fn - The asynchronous function to be called
* @param {Array} fnArgs - Optionally, the arguments to be passed to `fn`, expressed as an array
* @param {*} initialData - Optionally, default value(s) to returned as `data`
* @returns {[state, setArgs]}
*/
const useAsyncFunc = (fn, fnArgs = [], initialData) => {
// useState manages the async func arguments.
const [args, setArgs] = useState(fnArgs);
// useReducer manages the local complex state of the async func hook's lifecycle.
// See the source code for the full reducer!
// NOTE: it'd be easy to modify the reducer to fit your needs.
const [state, dispatch] = useReducer(asyncReducer, {
isLoading: false,
isError: false,
data: initialData,
});
useEffect(() => {
let didCancel = false;
const callAsyncFn = async () => {
// Begin each async func call with a clean slate.
dispatch({ type: 'ASYNC_INIT' });
try {
// Because this is a generic hook for any async func,
// simply call the function with the args!
const result = await fn(...args);
// Set success with result
if (!didCancel) {
dispatch({ type: 'ASYNC_SUCCESS', payload: result });
}
} catch (error) {
// Otherwise, set failure
if (!didCancel) {
dispatch({ type: 'ASYNC_FAILURE' });
}
}
};
// Actual invocation
callAsyncFn();
// Cleanup function to optionally prevent setting state for unmounted component.
// Ref: https://reactjs.org/docs/hooks-effect.html#example-using-hooks-1
// Ref: https://www.robinwieruch.de/react-warning-cant-call-setstate-on-an-unmounted-component
return () => {
didCancel = true;
};
// useEffect will consider `fn` and `args` when deciding whether to skip a re-render.
// In short, skip re-render if `fn` or `args` do not change.
}, [fn, args]);
// `state` provides the status updates: { isLoading, isError, data }
// `setArgs` allows you to update the arguments passed to the async func
return [state, setArgs];
};
Conclusion
Really, that is it! Again, this is a slight modification to genericize Robin Wieruch's already-thorough useDataApi()
hook. My other small contribution is a basic test suite. Enjoy!
Posted on May 13, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.