DEV Community

Aaron K Saunders
Aaron K Saunders

Posted on

3

How To Do Form Validation in Vue with Tanstack Form

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
Enter fullscreen mode Exit fullscreen mode

install tanstack form

npm install @tanstack/vue-form
Enter fullscreen mode Exit fullscreen mode

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: '',
  },
});
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

update script section, to call the form.handleSubmit when user clicks the submit button in the form

const handleSubmit = () => {
  form.handleSubmit();
};
Enter fullscreen mode Exit fullscreen mode

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';
        }
      },
    }"
  >
Enter fullscreen mode Exit fullscreen mode

on the note

  <form.Field
    name="note"
    :validators="{
      onChange: ({ value }) => {
        if (!value || value === '') {
          return 'this is a required value';
        }
      },
    }"
  >
Enter fullscreen mode Exit fullscreen mode

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: '',
  },
})
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
    }}
Enter fullscreen mode Exit fullscreen mode

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';
            }
          },
        }"
      >
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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

SurveyJS custom survey software

Simplify data collection in your JS app with a fully integrated form management platform. Includes support for custom question types, skip logic, integrated CCS editor, PDF export, real-time analytics & more. Integrates with any backend system, giving you full control over your data and no user limits.

Learn more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs