A tiny, no-dependencies, fully type-safe React hook for running async functions with built-in status management, cancellation, reset, state synchronization and optional side effects.
- π Run any async function (
fetchor any other library or custom) - ποΈ Track async operation status
idle | loading | success | error - π§ Stores data & error inside hook state
- β Supports cancellation (prevents state updates after cancel)
- π Supports reset (cancels and restores initial state)
- π Race-condition safe (only the latest call can update state)
- πͺΆ Lightweight (no dependencies)
- π§ Fully typed with TypeScript generics
- βοΈ React Compiler compatible (no need for useCallback)
The demo showcases:
- Running successful async operations
- Error handling on rejected async operations
- Canceling in-flight promises
- Resetting state
- Ensuring no race conditions (Only the latest invocation resolves β stale promises are ignored automatically.)
- Visualizing status transitions (IDLE β LOADING β SUCCESS / ERROR)
You can also explore the full demo source code inside the /demo folder of the repository.
npm:
npm install react-use-stateful-promiseyarn:
yarn add react-use-stateful-promiseTypeScript (with types):
import { useStatefulPromise, Status } from "react-use-stateful-promise";
import { User } from "./types";
function Example() {
const fetchUser = async (id: number) => {
const res = await fetch(`/api/user/${id}`);
return res.json();
};
const { status, data, error, run, cancel, reset } = useStatefulPromise<
User,
[number]
>(fetchUser, {});
return (
<div>
<button onClick={() => run(1)}>Load user</button>
{status === Status.LOADING && <p>Loading...</p>}
{status === Status.ERROR && <p>Error: {error?.message}</p>}
{status === Status.SUCCESS && <pre>{JSON.stringify(data.name)}</pre>}
<button onClick={cancel}>Cancel</button>
<button onClick={reset}>Reset</button>
</div>
);
}JavaScript (no types):
import { useStatefulPromise, Status } from "react-use-stateful-promise";
function Example() {
const fetchUser = async (id) => {
const res = await fetch(`/api/user/${id}`);
return res.json();
};
const { status, data, error, run, cancel, reset } = useStatefulPromise(
fetchUser,
{}
);
return (
<div>
<button onClick={() => run(1)}>Load user</button>
{status === Status.LOADING && <p>Loading...</p>}
{status === Status.ERROR && <p>Error: {error?.message}</p>}
{status === Status.SUCCESS && <pre>{JSON.stringify(data.name)}</pre>}
<button onClick={cancel}>Cancel</button>
<button onClick={reset}>Reset</button>
</div>
);
}const { status, data, run, cancel, reset } = useStatefulPromis(
asyncFunction,
initialData,
{
// optional callbacks for each action
onSuccess: (newData) => newData,
onError: (error) => error,
onCancel: () => null,
onReset: () => null,
}
);| Name | Type | Description |
|---|---|---|
status |
Status ("IDLE" | "LOADING" | "ERROR" | "SUCCESS") |
The current state of the async call. |
data |
T |
The resolved value from the async function. |
error |
Error | null |
The caught error if the async function rejects. |
run(...args) |
(...args: Args) => Promise<T> |
Runs the async function with the given arguments, updates state accordingly. Returns the underlying promise for chaining. |
cancel() |
() => void |
Cancels the current pending async call and resets the hook to IDLE. Prevents updates from the canceled promise. |
reset() |
() => void |
Fully resets state, clears data and error, and cancels any pending promise. |
| Parameter | Type | Required | Description |
|---|---|---|---|
asyncFunction |
(β¦args: Args) => Promise<T> |
βοΈ Yes | Any function returning a Promise. The hook manages its lifecycle. |
initialData |
T |
βοΈ Yes | Initial value for data. |
options |
object |
βοΈ No | Optional callbacks triggered on success, error, cancel, or reset. |
options.onSuccess |
(data: T) => void |
βοΈ No | Called when the async function resolves. |
options.onError |
(error: Error) => void |
βοΈ No | Called when the async function rejects. |
options.onCancel |
() => void |
βοΈ No | Called when the async operation is cancelled. |
options.onReset |
() => void |
βοΈ No | Called when reset() is invoked. |
Contributions are welcome and appreciated, be it opening issues, opening pull requests or just asking questions.
The repository includes the demo environment for easier testing of the hook.
To start working, fork and clone the repository locally and then
cd react-use-stateful-promise
npm i # Install deps
npm run test # Run tests
npm run dev # Spin up demo environmentThe hook code is located in the /lib folder.
The demo code is located in the /demo folder.