Guide
useAction
useAction is a Vue composable for executing type-safe server actions with reactive status, validation errors, and lifecycle callbacks.
useAction
useAction is a Vue composable that wraps action execution with reactive state tracking, callbacks, and error handling.
Basic Usage
app/components/GreetForm.vue
<script setup lang="ts">
import { greet } from '#safe-action/actions'
const { execute, data, isExecuting } = useAction(greet)
</script>
<template>
<button @click="execute({ name: 'World' })" :disabled="isExecuting">
{{ isExecuting ? 'Loading...' : 'Greet' }}
</button>
<p v-if="data">{{ data.greeting }}</p>
</template>
With Callbacks
Pass callbacks as the second argument to react to lifecycle events:
<script setup lang="ts">
import { createPost } from '#safe-action/actions'
const { execute, data, isExecuting, hasSucceeded } = useAction(createPost, {
onSuccess({ data, input }) {
console.log('Created post:', data.title)
// Navigate, show toast, etc.
},
onError({ error, input }) {
console.error('Failed:', error)
},
onSettled({ result, input }) {
// Runs after every execution, success or error
},
onExecute({ input }) {
// Runs when execution starts
},
})
</script>
Async Execution
Use executeAsync when you need to await the result:
<script setup lang="ts">
import { createPost } from '#safe-action/actions'
const { executeAsync } = useAction(createPost)
async function handleSubmit() {
const result = await executeAsync({ title: 'Hello', body: 'World' })
if (result.data) {
navigateTo(`/posts/${result.data.id}`)
}
}
</script>
Handling Validation Errors
validationErrors contains per-field error arrays returned by Zod validation:
<script setup lang="ts">
import { createPost } from '#safe-action/actions'
const { execute, validationErrors } = useAction(createPost)
</script>
<template>
<form @submit.prevent="execute({ title: '', body: '' })">
<div>
<input placeholder="Title" />
<span v-if="validationErrors?.title" class="error">
{{ validationErrors.title[0] }}
</span>
</div>
<div>
<textarea placeholder="Body" />
<span v-if="validationErrors?.body" class="error">
{{ validationErrors.body[0] }}
</span>
</div>
<button type="submit">Create</button>
</form>
</template>
Resetting State
Call reset() to return all reactive state to its initial values:
<script setup lang="ts">
import { createPost } from '#safe-action/actions'
const { execute, data, reset, hasSucceeded } = useAction(createPost)
</script>
<template>
<div v-if="hasSucceeded">
<p>Post created: {{ data?.title }}</p>
<button @click="reset()">Create Another</button>
</div>
<form v-else @submit.prevent="execute({ title: 'Hello', body: 'World' })">
<button type="submit">Create Post</button>
</form>
</template>
Status Tracking
The status ref tracks the current execution state:
<script setup lang="ts">
import { createPost } from '#safe-action/actions'
const { execute, status, isIdle, isExecuting, hasSucceeded, hasErrored } = useAction(createPost)
</script>
<template>
<div>
<p>Status: {{ status }}</p>
<p v-if="isIdle">Ready to submit</p>
<p v-if="isExecuting">Submitting...</p>
<p v-if="hasSucceeded">Done!</p>
<p v-if="hasErrored">Something went wrong</p>
</div>
</template>