Skip to content

Source code and demo for generic async operation React hook.

Notifications You must be signed in to change notification settings

kwhitejr/use-async-func

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

use-async-func

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!

About

Source code and demo for generic async operation React hook.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published