useAsyncLoading saved progress…

useAsync

useFetch is great when the thing you load is a URL, but plenty of async work isn't a GET — submitting a form, calling an SDK, running a computation in a worker. useAsync generalizes the lifecycle: hand it any function that returns a promise and it tracks the four states every async operation moves through — idle, pending, success, error — and hands you an execute trigger so you decide when it runs.

Implement useAsync(asyncFunction, immediate = true). It returns { execute, status, value, error }. execute(...args) runs the function, flipping status to pending, then to success with the resolved value or error with the rejection. When immediate is true it runs once on mount; otherwise it waits for a manual execute.

Signature

function useAsync(asyncFunction, immediate = true) {
  // returns { execute, status, value, error }
  // status: 'idle' | 'pending' | 'success' | 'error'
}

Examples

// Manual trigger — run on button click, not on mount.
const { execute, status } = useAsync(submitForm, false);
<button disabled={status === 'pending'} onClick={() => execute(formData)}>
  {status === 'pending' ? 'Saving…' : 'Save'}
</button>
// Immediate — load on mount.
const { status, value, error } = useAsync(() => loadDashboard(userId));

Notes

  • Four states — start idle; execute sets pending (and clears prior value/error); resolve → success + value; reject → error + error.
  • execute forwards argsexecute(a, b) calls asyncFunction(a, b), and returns the promise so callers can await it.
  • immediate gates the mount runtrue runs on mount via an effect; false leaves it idle until called.
  • Stable execute — memoize it on asyncFunction so it's safe in effect deps and event handlers. Assume asyncFunction is stable (memoized by the caller).
Loading editor…