useAsyncFn

Wrap an async function to get loading, error and result state, ignoring results of outdated calls.

Use useAsyncFn when a button or form runs an async action and you need a loading flag, the result or the error, without writing that state yourself.

Example

app/components/product/SaveForLater.tsx
import { useAsyncFn } from '@salla.sa/twilight-theme-engine/hooks/useAsyncFn';

export function SaveForLater({ productId }: { productId: number }) {
  const [state, save] = useAsyncFn(async () => {
    await window.salla?.wishlist.add(productId);
    return true;
  }, [productId]);

  return (
    <button onClick={() => save()} disabled={state.loading}>
      {state.value ? 'Saved' : 'Save for later'}
    </button>
  );
}

It returns a pair: the current state, and a wrapped version of your function. Call the wrapped function to run the action.

API

function useAsyncFn<T extends (...args: never[]) => Promise<unknown>>(
  fn: T,
  deps?: DependencyList,
  initialState?: AsyncState<Awaited<ReturnType<T>>>
): [AsyncState<Awaited<ReturnType<T>>>, T];
PropTypeDescription
fnTThe async function to wrap. Required.
deps?DependencyListValues fn reads, like the dependencies of useCallback. Default: [].
initialState?AsyncStateStarting state. Default: { loading: false }.

The state has loading: boolean, and then value after success or error after failure.

Gotchas

When fn rejects, the wrapped function does not throw. It resolves with the error, and state.error is set. Check state.error or result instanceof Error instead of using try/catch.

  • deps defaults to [], so the wrapped function keeps the first fn it saw. Pass every value fn reads, as in the example.
  • Only the latest call updates the state. Results of older, slower calls are ignored.
  • State is not updated after the component unmounts.

Next

On this page