Skip to content

Vue Logo Vue Integration

The @asyncflowstate/vue package provides Vue 3 Composition API composables with reactive refs and provide/inject configuration.

Installation

bash
npm install @asyncflowstate/vue @asyncflowstate/core

Core Composable (useFlow)

The useFlow composable is the main feature you'll interact with in Vue. It exposes all state as fully reactive Vue Refs.

Example

vue
<script setup lang="ts">
import { useFlow } from "@asyncflowstate/vue";

const { loading, data, error, execute, button } = useFlow(
  async (id: string) => {
    const response = await fetch(`/api/users/${id}`);
    return response.json();
  },
  {
    onSuccess: (user) => console.log("Fetched:", user.name),
    onError: (err) => console.error("Failed:", err),
  },
);
</script>

<template>
  <button v-bind="button()">
    {{ loading ? "Loading..." : "Fetch User" }}
  </button>

  <div v-if="error" class="error">
    {{ error.message }}
  </div>

  <div v-if="data" class="user-card">
    <h2>{{ data.name }}</h2>
    <p>{{ data.email }}</p>
  </div>
</template>

Return Value

All return values are Vue ref objects for full reactivity:

PropertyTypeDescription
statusRef<string>Current flow state
loadingRef<boolean>Loading indicator
dataRef<T | null>Last result
errorRef<Error | null>Last error
execute(...args) => Promise<T>Trigger the action
reset() => voidReset to idle
button() => objectAccessible button attrs

Button Attributes Helper

Easily generate all the accessible and stateful attributes needed for your interactive buttons using the button() helper feature.

Example

vue
<template>
  <button v-bind="button()">
    {{ loading ? "Saving..." : "Save" }}
  </button>
</template>

Generates: { onClick, disabled, 'aria-busy', 'aria-disabled' }

Form Handling

Bind schemas and handle complex form layouts effortlessly using form() for Vue templates.

Example

vue
<script setup>
import { useFlow } from "@asyncflowstate/vue";
import { z } from "zod";

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

const { form, fieldErrors, loading, error } = useFlow(updateProfile);
</script>

<template>
  <form v-bind="form({ schema, extractFormData: true })">
    <input name="name" />
    <span v-if="fieldErrors.name">{{ fieldErrors.name }}</span>

    <input name="email" type="email" />
    <span v-if="fieldErrors.email">{{ fieldErrors.email }}</span>

    <button type="submit" :disabled="loading">
      {{ loading ? "Saving..." : "Save" }}
    </button>
  </form>
</template>

Global Configuration

Provide app-wide defaults at the root level using Vue's Provide/Inject pattern.

Example

Use provide/inject for app-wide defaults:

ts
// main.ts or App.vue setup
import { provideFlowConfig } from "@asyncflowstate/vue";

provideFlowConfig({
  retry: { maxAttempts: 3, backoff: "exponential" },
  loading: { minDuration: 400 },
  onError: (err) => ElMessage.error(err.message),
});

Advanced Workflows

Sequential Workflows

Chain async operations so the output of one immediately feeds the next, using the useFlowSequence feature.

Example

vue
<script setup>
import { useFlowSequence } from "@asyncflowstate/vue";

const sequence = useFlowSequence([
  { name: "Validate", flow: validateFlow },
  { name: "Upload", flow: uploadFlow, mapInput: (prev) => prev.id },
]);
</script>

State Observation

Because all returned values are standard Vue Refs, you can attach native watchers to react to flow lifecycle changes natively.

Example

vue
<script setup>
import { watch } from "vue";

const { status, data } = useFlow(fetchData);

watch(status, (newStatus) => {
  if (newStatus === "success") {
    router.push("/dashboard");
  }
});
</script>

Best Practices

Building a professional async experience in Vue requires leveraging the Composition API effectively. Follow these patterns for the best results.

Composition API Patterns

Destructuring correctly

Always destructure the return of useFlow if you need single properties. Because they are standard Vue Refs, they maintain reactivity.

ts
const { loading, data, execute } = useFlow(fetchUser);

Prefer Callbacks over Watchers

While you can use watch(status), it's often cleaner to use the built-in onSuccess and onError callbacks. This keeps your redirect or toast logic co-located with the flow definition.

Global State & Configuration

Placement of provideFlowConfig

Call provideFlowConfig at the highest possible level (usually App.vue or main.ts). This ensures every component in your application tree can access the defaults without manual prop passing.

Template UX

Using v-bind

The v-bind="button()" pattern is the most powerful way to handle accessibility. It automatically manages :disabled, aria-busy, and the click event, ensuring your Vue components are accessible by default.

Loading State "Flicker"

Just like in React, very fast backend responses can cause a flicker. Use loading: { minDuration: 400 } in your global config to provide a more stable visual experience for your users.

Built with by AsyncFlowState Contributors
Open Source · MIT License