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)