@arcticzeroo/react-promise-hook
v1.5.0
Published
Promise hooks for react
Readme
@arcticzeroo/react-promise-hook
React hooks for managing promise state. Provides a simple way to track the lifecycle of async operations — loading, success, and error — without boilerplate.
Installation
npm install @arcticzeroo/react-promise-hookHooks
useImmediatePromiseState<T>(returnsPromise: () => Promise<T>): IRunnablePromiseState<T>
Runs the promise immediately when the callback changes. Wrap your callback in useCallback to avoid infinite loops.
const loadUsers = useCallback(() => fetch('/api/users').then(r => r.json()), []);
const { value, error, stage, run } = useImmediatePromiseState(loadUsers);
if (stage === PromiseStage.error) {
return <RetryButton onClick={run} />;
}
if (stage !== PromiseStage.success) {
return <Spinner />;
}
return <UserList users={value} />;useDelayedPromiseState<T>(returnsPromise: () => Promise<T>, keepLastValue?: boolean): IDelayedPromiseState<T>
Like useImmediatePromiseState, but does not run automatically — call run() yourself. When keepLastValue is true (the default), the previous value is preserved during re-fetches to avoid UI flashing.
Returns an additional actualStage field reflecting the real-time stage, while stage holds the last completed stage when keepLastValue is enabled.
const search = useCallback(() => fetch(`/api/search?q=${query}`).then(r => r.json()), [query]);
const { value, stage, actualStage, run } = useDelayedPromiseState(search);
useEffect(() => { run(); }, [run]);useExistingPromiseState<T>(promise: Promise<T>): IPromiseState<T>
Tracks state of an already-existing promise. Stage starts at running.
useMaybeExistingPromiseState<T>(promise: Promise<T> | undefined | null, initialStage?: PromiseStage): IPromiseState<T>
Like useExistingPromiseState, but accepts undefined/null. Stage starts at initialStage (default: notRun) until a promise is provided.
Types
enum PromiseStage {
notRun,
running,
error,
success,
}
interface IPromiseState<T> {
stage: PromiseStage;
value: T | undefined;
error: any;
}
interface IRunnablePromiseState<T> extends IPromiseState<T> {
run: () => void;
}
interface IDelayedPromiseState<T> extends IRunnablePromiseState<T> {
actualStage: PromiseStage;
}ESLint Plugin
The package ships with an ESLint rule to catch a common mistake: destructuring only value from a promise state hook without handling errors. This leads to silently ignored failures and missing loading states.
The Problem
// ❌ Bad: error and loading states are silently ignored
const { value } = useImmediatePromiseState(fetchData);
return <div>{value}</div>;
// ✅ Good: stage is checked to handle all states
const { value, stage } = useImmediatePromiseState(fetchData);
// ✅ Also good: both value and error are destructured
const { value, error } = useImmediatePromiseState(fetchData);The require-promise-state-stage rule enforces that when destructuring a promise state hook, you must include either:
stageoractualStage, or- both
valueanderror
Setup
Flat config (eslint.config.js)
import promiseHook from '@arcticzeroo/react-promise-hook/eslint';
export default [
promiseHook.configs.recommended,
// ... your other config
];Legacy config (.eslintrc)
{
"plugins": ["@arcticzeroo/react-promise-hook"],
"rules": {
"@arcticzeroo/react-promise-hook/require-promise-state-stage": "error"
}
}