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

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Top comments (0)

Billboard image

Create up to 10 Postgres Databases on Neon's free plan.

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Try Neon for Free β†’

πŸ‘‹ Kindness is contagious

Engage with a sea of insights in this enlightening article, highly esteemed within the encouraging DEV Community. Programmers of every skill level are invited to participate and enrich our shared knowledge.

A simple "thank you" can uplift someone's spirits. Express your appreciation in the comments section!

On DEV, sharing knowledge smooths our journey and strengthens our community bonds. Found this useful? A brief thank you to the author can mean a lot.

Okay