npm install @asyncflowstate/react
# or
pnpm add @asyncflowstate/reactThis package depends on @asyncflowstate/core which will be installed automatically.
import { useFlow } from "@asyncflowstate/react";
function SaveButton() {
const flow = useFlow(async (data) => {
return await api.save(data);
});
return (
<button {...flow.button()}>
{flow.loading ? "Saving..." : "Save Changes"}
</button>
);
}Use FlowProvider to set default options for all flows in your application. This is perfect for global error handling, retry policies, and UX settings.
import { FlowProvider, useFlow } from "@asyncflowstate/react";
function App() {
return (
<FlowProvider
config={{
onError: (err) => toast.error(err.message),
retry: { maxAttempts: 3, backoff: "exponential" },
loading: { minDuration: 300 },
}}
>
<YourApp />
</FlowProvider>
);
}
// All flows inside will inherit the global config
function YourApp() {
const flow = useFlow(saveData); // Automatically has retry + error handling
return <button {...flow.button()}>Save</button>;
}- DRY Principle: Define error handlers, retry logic, and UX settings once
- Consistency: Ensure all async actions behave the same way
- Flexibility: Local options can override global settings
- Nested Providers: Different sections can have different configurations
<FlowProvider config={{ retry: { maxAttempts: 2 } }}>
<MainApp />
{/* Admin section needs more retries */}
<FlowProvider config={{ retry: { maxAttempts: 5 } }}>
<AdminPanel />
</FlowProvider>
</FlowProvider>See FlowProvider examples for more patterns.
Injects disabled and aria-busy props automatically based on the flow's loading state. If no onClick is provided, it defaults to calling flow.execute() with no arguments.
<button {...flow.button({ onClick: () => alert("Custom!") })}>Submit</button>Handles onSubmit with e.preventDefault() and provides advanced orchestration features:
extractFormData: Automatically extracts form values into a plain object.validate: Hook for pre-execution validation.resetOnSuccess: Automatically resets the form fields after successful completion.
<form
{...flow.form({
extractFormData: true,
validate: (data) => (!data.title ? { title: "Required" } : null),
resetOnSuccess: true,
})}
>
<input name="title" />
{flow.fieldErrors.title && <span>{flow.fieldErrors.title}</span>}
<button type="submit" disabled={flow.loading}>
Save
</button>
</form>A component that automatically announces success and error messages to screen readers using ARIA live regions.
const flow = useFlow(saveAction, {
a11y: {
announceSuccess: "Profile updated successfully!",
announceError: (err) => `Failed to save: ${err.message}`,
},
});
return (
<div>
<flow.LiveRegion />
{/* ... form ... */}
</div>
);Automatically manages focus for error messages. When the flow enters an error state, the element with this ref will be focused for accessibility.
{
flow.error && (
<div ref={flow.errorRef} tabIndex={-1} className="error-banner">
{flow.error.message}
</div>
);
}Control how users perceive loading states to prevent UI flickering.
const flow = useFlow(action, {
loading: {
minDuration: 500, // Stay loading for at least 500ms
delay: 200, // Don't show loading if action finishes in <200ms
},
});Track and display the progress of long-running operations.
const flow = useFlow(async () => {
flow.setProgress(20);
// ... step 1 ...
flow.setProgress(100);
});
return <progress value={flow.progress} max="100" />;Chain flows without manual useEffect. Flows can subscribe to signals from other flows.
const login = useFlow(loginAction);
const fetchProfile = useFlow(fetchAction, {
triggerOn: [login.signals.success],
});Native support for streaming responses. The flow status will be 'streaming' while chunks arrive. flow.data accumulates string chunks automatically.
const flow = useFlow(chatAction);
return (
<div>
{flow.status === "streaming" && <span>GPT is typing...</span>}
<pre>{flow.data}</pre>
</div>
);Run multiple flows in a specific order with aggregate state and progress.
import { useFlowSequence } from "@asyncflowstate/react";
const sequence = useFlowSequence([
{ name: "Upload", flow: uploadFlow },
{ name: "Process", flow: processFlow, mapInput: (file) => file.id },
]);A powerful drop-in component to visualize all async activity. Features a Timeline/Gantt view to identify bottlenecks and parallel execution order.
import { FlowDebugger } from "@asyncflowstate/react";
// Add it once at the root
<FlowDebugger />;If a page is refreshed during a validation error or an interrupted loading state, useFlow automatically restores the state and re-focuses the last active field (or errorRef) to provide a seamless recovery experience.
Catch success or error events from ANY flow globally.
import { FlowNotificationProvider } from "@asyncflowstate/react";
<FlowNotificationProvider
onSuccess={(e) => toast.success(`${e.flowName} completed!`)}
onError={(e) => toast.error(`${e.flowName} failed: ${e.state.error.message}`)}
>
<App />
</FlowNotificationProvider>;{
// State
status: 'idle' | 'loading' | 'success' | 'error';
data: TData | null;
error: TError | null;
loading: boolean; // Respects loading.delay
progress: number; // 0-100
fieldErrors: Record<string, string>;
// Actions
execute: (...args: TArgs) => Promise<TData | undefined>;
reset: () => void;
cancel: () => void;
setProgress: (val: number) => void;
// Helpers & Components
button: (props?) => ButtonHTMLAttributes;
form: (props?) => FormHTMLAttributes;
LiveRegion: React.ComponentType;
errorRef: React.RefObject<any>;
}