@submit.prevent — Run Async, Catch Errors
Submitting & Error Handling
The submit flow follows a small, consistent pattern. Prevent default, run async work, show errors, and reset or navigate on success.
What you'll learn
- Use @submit.prevent on the form
- Disable controls while submitting
- Surface server errors per field
Every form ends with submit. The pattern is small enough to memorize: prevent the default, run async work, handle errors per field, and clean up on success.
The Pattern
<script setup lang="ts">
import { ref, reactive } from 'vue'
const form = reactive({ email: '', password: '' })
const errors = reactive<Record<string, string>>({})
const submitting = ref(false)
const submitError = ref<string | null>(null)
async function onSubmit() {
submitting.value = true
submitError.value = null
for (const key of Object.keys(errors)) delete errors[key]
try {
await api.signIn(form)
// success — navigate or reset
} catch (err: any) {
if (err.status === 400 && err.fieldErrors) {
Object.assign(errors, err.fieldErrors)
} else {
submitError.value = err.message ?? 'Something went wrong'
}
} finally {
submitting.value = false
}
}
</script>
<template>
<form @submit.prevent="onSubmit">
<input v-model="form.email" :aria-invalid="!!errors.email" />
<p v-if="errors.email">{{ errors.email }}</p>
<input v-model="form.password" type="password" :aria-invalid="!!errors.password" />
<p v-if="errors.password">{{ errors.password }}</p>
<p v-if="submitError" class="form-error">{{ submitError }}</p>
<button :disabled="submitting">
{{ submitting ? 'Signing in…' : 'Sign in' }}
</button>
</form>
</template> Why @submit.prevent
Using a form element gives you native goodies: Enter submits, screen readers announce it, the browser remembers values. @submit.prevent keeps all of that while letting you handle submission in JS.
Server-Driven Field Errors
When the server returns 4xx validation errors (a { fieldErrors: { email: '...' } } payload), merge them into the same errors object your client validation uses. The UI does not care where errors come from.
After Success
Three common follow-ups, pick whichever fits your flow:
- Reset the form:
Object.assign(form, { email: '', password: '' }) - Navigate:
router.push('/dashboard') - Show a toast and stay put
That wraps up the forms section — next we look at routing with vue-router.
Vue Router — Introduction →