Overview
This blog post show you a simple example of how to quickly integrate form validation in your vuejs or nuxt js application using Tanstack Form.
Here is a full video with a more complex example for you to review if you want to dive deeper
Let's Get Started
npm create vite@latest my-vue-app --template vue-ts
cd my-vue-app
install tanstack form
npm install @tanstack/vue-form
create a file SimpleForm.vue
and lets create the form instance
interface Thought {
title: string;
note: number;
}
const form = useForm<Thought>({
onSubmit: async ({ value }) => {
// Display form data on success
alert(JSON.stringify(value));
},
defaultValues: {
note: '',
title: '',
},
});
add this as the template since this is the normal way to implement a form with v-model for capturing the values from user input
<template>
<fieldset>
<legend class="text-xl">Add New Thought</legend>
<label class="font-bold">
Title:
<input v-model="thought.title" type="text" />
</label>
<br />
<label>
Note:
<input v-model="thought.note" type="text" />
</label>
<br />
<button @click="addThought">Add Friend</button>
</fieldset>
</template>
Now lets add the Tanstack Form code,
<template>
<fieldset>
<legend class="text-xl">Add new thought</legend>
<form.Field name="title">
<template v-slot="{ field }">
<label class="font-bold">
Title:
<input
:name="field.name"
:value="field.state.value"
@blur="field.handleBlur"
@input="(e) => field.handleChange(e.target.value)"
type="text"
/>
</label>
</template>
</form.Field>
<br />
<form.Field name="note">
<template v-slot="{ field }">
<label class="font-bold">
Note:
<input
:name="field.name"
:value="field.state.value"
@blur="field.handleBlur"
@input="(e) => field.handleChange(e.target.value)"
type="text"
/>
</label>
</template>
</form.Field>
<br />
<button @click="addThought">Add Friend</button>
</fieldset>
</template>
notice how we have wrapped the input
element for the form.Field
element, using the slot to pass information on the field and capturing input events for when user enters the value.
at this point the values from the form are accessible from the form
object we created in the script section of the application.
const { title, note } = form.state.values;
but we are using this tool because we want to have a structured way of validating the form.
add the following to the template, wrap the fieldset in a form
<form @submit.prevent="handleSubmit">
...
</form>
update script section, to call the form.handleSubmit
when user clicks the submit button in the form
const handleSubmit = () => {
form.handleSubmit();
};
Now lets add a simple validator to make sure the fields are not empty
on the title
<form.Field
name="title"
:validators="{
onChange: ({ value }) => {
if (!value || value === '') {
return 'this is a required value';
}
},
}"
>
on the note
<form.Field
name="note"
:validators="{
onChange: ({ value }) => {
if (!value || value === '') {
return 'this is a required value';
}
},
}"
>
update the form
instance code to display alert on success
const form = useForm<Thought>({
onSubmit: async ({ value }) => {
// Do something with form data
alert(JSON.stringify(value));
},
defaultValues: {
note: '',
title: '',
},
})
update template to display message when there is an error
<form.Field
name="title"
:validators="{
onChange: ({ value }) => {
if (!value || value === '') {
return 'this is a required value';
}
},
}"
>
<template v-slot="{ field }">
<label class="font-bold">
Title:
<input
:name="field.name"
:value="field.state.value"
@blur="field.handleBlur"
@input="(e) => field.handleChange(e.target.value)"
type="text"
/>
</label>
<div>
{{
field.state.meta.errors?.length &&
field.state.meta.isTouched
? field.state.meta.errors[0]
: null
}}
</div>
</template>
</form.Field>
<br />
<form.Field
name="note"
:validators="{
onChange: ({ value }) => {
if (!value || value === '') {
return 'this is a required value';
}
},
}"
>
<template v-slot="{ field }">
<label class="font-bold">
Note:
<input
:name="field.name"
:value="field.state.value"
@blur="field.handleBlur"
@input="(e) => field.handleChange(e.target.value)"
type="text"
/>
</label>
<div>
{{
field.state.meta.errors?.length &&
field.state.meta.isTouched
? field.state.meta.errors[0]
: null
}}
</div>
</template>
</form.Field>
So now u should get error message if attempting to submit with empty fields, but we would like to disable the submit button if the form is not valid at all.
to do this we need to validate the form onMount
and subscribe to form changes to update the submit button user interface.
But first lets update our error message so it is only displayed if the fields has been touched.
{{
field.state.meta.errors?.length &&
field.state.meta.isTouched
? field.state.meta.errors[0]
: null
}}
now lets validate onMount
also
<form.Field
name="note"
:validators="{
onChange: ({ value }) => {
if (!value || value === '') {
return 'this is a required value';
}
},
onMount: ({ value }) => {
if (!value || value === '') {
return 'this is a required value';
}
},
}"
>
finally lets update the code on the submit button
<form.Subscribe>
<template v-slot="{ canSubmit, isSubmitting }">
<button type="submit" :disabled="!canSubmit">
{{ isSubmitting ? '...' : 'Add Thought' }}
</button>
</template>
</form.Subscribe>
Simple Example in Stackblitz
Full Project w/ Source Code
This was a quick run through of a simple example. I have a more complex example here Tanstack Form with Zod Validation
Top comments (0)