Skip to content

Getting Started

Idle
Loading
Success

Prerequisites

  • Node.js ≥ 16.0.0
  • A project using React, Vue, Svelte, Angular, SolidJS, or vanilla JS

Installation

Choose the package for your framework:

bash
npm install @asyncflowstate/react @asyncflowstate/core
bash
npm install @asyncflowstate/next @asyncflowstate/react @asyncflowstate/core
bash
npm install @asyncflowstate/vue @asyncflowstate/core
bash
npm install @asyncflowstate/svelte @asyncflowstate/core
bash
npm install @asyncflowstate/angular @asyncflowstate/core
bash
npm install @asyncflowstate/solid @asyncflowstate/core

Using pnpm?

Replace npm install with pnpm add in the commands above.

Your First Flow

React

tsx
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>
  );
}

That's it. With one hook, you get:

  • Automatic loading state
  • Double-click prevention (button is disabled while loading)
  • ARIA attributes for accessibility
  • Error state management

What flow.button() Returns

The button() helper generates the props your button needs:

ts
{
  onClick: () => flow.execute(),
  disabled: flow.loading,
  "aria-busy": flow.loading,
  "aria-disabled": flow.loading,
}

Adding Error Handling

tsx
function SaveButton() {
  const flow = useFlow(async (data) => api.save(data), {
    onSuccess: () => toast.success("Saved!"),
    onError: (err) => toast.error(err.message),
  });

  return (
    <div>
      <button {...flow.button()}>{flow.loading ? "Saving..." : "Save"}</button>
      {flow.error && (
        <p ref={flow.errorRef} role="alert">
          {flow.error.message}
        </p>
      )}
    </div>
  );
}

Form Handling

tsx
import { z } from "zod";

const schema = z.object({
  username: z.string().min(3),
  email: z.string().email(),
});

function ProfileForm() {
  const flow = useFlow(updateProfile);

  return (
    <form {...flow.form({ schema, extractFormData: true })}>
      <input name="username" />
      {flow.fieldErrors.username && <span>{flow.fieldErrors.username}</span>}

      <input name="email" />
      {flow.fieldErrors.email && <span>{flow.fieldErrors.email}</span>}

      <button type="submit" disabled={flow.loading}>
        Submit
      </button>
    </form>
  );
}

Global Configuration

Wrap your app with FlowProvider to set defaults for all flows:

tsx
import { FlowProvider } from "@asyncflowstate/react";

function App() {
  return (
    <FlowProvider
      config={{
        onError: (err) => toast.error(err.message),
        retry: { maxAttempts: 3, backoff: "exponential" },
        loading: { minDuration: 400 },
      }}
    >
      <YourApp />
    </FlowProvider>
  );
}

Next Steps

Built with by AsyncFlowState Contributors
Open Source · MIT License