npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@usefy/use-async-fn

v1.1.0

Published

A React hook for running a manual-trigger async function with idle/pending/success/error lifecycle tracking and race-safe stale-response guarding

Readme


Overview

useAsyncFn is part of the @usefy ecosystem — a collection of production-ready, TypeScript-first, SSR-safe React hooks. It is the manual-trigger core for running a single async function and tracking its lifecycle, designed for event-driven invocation (button clicks, form submits) and built to be the foundation higher-level data hooks (useAsync, usePolling) build on.

Nothing runs until you call run(...). Every call is race-guarded so out-of-order resolutions never clobber fresh data, and the component is never updated after it unmounts.

Features

  • Full lifecycle state{ data, error, status, isLoading } where status (idle/pending/success/error) is the source of truth and isLoading mirrors pending
  • Race-safe — call run again before the previous call settles and only the latest call may update state; a slow earlier promise resolving late is ignored
  • Unmount-safe — no state updates (and no onSuccess/onError callbacks) after the component unmounts
  • Stable run — memoized identity, safe as an effect dependency or child prop; reads the latest inline fn through a ref so you never need to memoize it
  • Never rejectsrun resolves with the value (or undefined on failure) so an un-awaited call can't throw an unhandled rejection; errors surface via state.error
  • SSR-safe & StrictMode-safe — touches no browser globals; the mounted flag is re-armed on re-mount
  • TypeScript-first — full type inference and exported types
  • Tiny & tree-shakeable — published as its own package

Installation

# npm
npm install @usefy/use-async-fn

# yarn
yarn add @usefy/use-async-fn

# pnpm
pnpm add @usefy/use-async-fn

Requires React 18 or 19 (peerDependencies: "react": "^18.0.0 || ^19.0.0").

Quick Start

import { useAsyncFn } from "@usefy/use-async-fn";

function LoginForm() {
  const [state, run] = useAsyncFn(async (email: string, password: string) => {
    const res = await fetch("/api/login", {
      method: "POST",
      body: JSON.stringify({ email, password }),
    });
    if (!res.ok) throw new Error("Invalid credentials");
    return (await res.json()) as { token: string };
  });

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    run("[email protected]", "hunter2"); // fire-and-forget is safe — run never rejects
  };

  return (
    <form onSubmit={handleSubmit}>
      <button disabled={state.isLoading}>
        {state.isLoading ? "Signing in…" : "Sign in"}
      </button>
      {state.status === "error" && <p role="alert">{state.error?.message}</p>}
      {state.status === "success" && <p>Welcome! Token: {state.data?.token}</p>}
    </form>
  );
}

API

const [state, run] = useAsyncFn<T, Args, E>(fn, options?);

Parameters

| Parameter | Type | Description | | --------- | ---- | ----------- | | fn | (...args: Args) => Promise<T> | The async function to run. Receives whatever args you pass to run. Read through a ref — an inline function is fine and never goes stale. | | options | UseAsyncFnOptions<T, E> | Optional. See below. |

Options — UseAsyncFnOptions<T, E>

| Option | Type | Description | | ------ | ---- | ----------- | | initialData | T | Seed value for state.data before the first successful run. Status still starts "idle". | | onSuccess | (data: T) => void | Called after a run resolves — only for the latest (non-superseded) run, only while mounted. Fired from the event turn, never inside a state updater. | | onError | (error: E) => void | Called after a run fails — same latest-only, mounted-only, post-setState guarantees. |

Return — [state, run]

state: AsyncState<T, E>

| Field | Type | Description | | ----- | ---- | ----------- | | data | T \| undefined | The most recent successfully-resolved value. Retained across later pending/error transitions; only replaced on success. | | error | E \| undefined | The error from the most recent failed run. Cleared when a run starts and when a run succeeds. | | status | "idle" \| "pending" \| "success" \| "error" | The lifecycle status — the source of truth. | | isLoading | boolean | Convenience mirror of status === "pending". |

run: (...args: Args) => Promise<T | undefined> — Stable across renders. Forwards args to fn, moves state to pending, then to success or error.

Behavioural guarantees (by design)

  • What run resolves with: the value fn produced for that specific call on success, or undefined on failure. run never rejects — errors are surfaced via state.error, so a fire-and-forget run() can never cause an unhandled promise rejection. (Because failure resolves to undefined, a T of undefined is ambiguous with failure — read state.error to disambiguate.)
  • Data on error: the last successful data is kept (not cleared) when a later run fails, so you can keep showing stale data alongside an error. error is cleared the moment a new run starts.
  • Race / stale-response guarding: each call gets a monotonically increasing id; if a newer run starts before an older one settles, the older result is ignored for state purposes (and its onSuccess/onError is skipped). Only the latest call wins. Each run() promise still resolves with its own result.
  • AbortController: intentionally not wired into fn's signature here, to keep the generic Args clean. In-flight results from superseded calls are discarded by the stale-guard rather than aborted. Abortable fetching is layered on by the higher-level useAsync/usePolling hooks.

Exported types

AsyncStatus, AsyncFn<T, Args>, AsyncState<T, E>, AsyncRunFn<T, Args>, UseAsyncFnOptions<T, E>, UseAsyncFnReturn<T, Args, E>.

Testing

📊 View Detailed Coverage Report (GitHub Pages) — 23 tests, 100% statement coverage.

License

MIT © mirunamu

This package is part of the usefy monorepo.