Categories
Vue 3

Form Validation in a Vue 3 App with Vee-Validate 4 — Async and Cross-Field Validation

Form validation is an important part of any app.

In this article, we’ll look at how to use Vee-Validate 4 in our Vue 3 app for form validation.

Async Validation

We can validate form fields asynchronously with Vee-Validate 4.

For example, we can write:

<template>
  <div>
    <Form @submit="onSubmit">
      <label for="email">Email</label>
      <Field id="email" name="email" :rules="validateEmail" type="email" />
      <ErrorMessage name="email" />

      <button type="submit">Submit</button>
    </Form>
  </div>
</template>

<script>
import { Field, Form, ErrorMessage } from "vee-validate";

const mockApiRequest = (value) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(value === "abc@abc.com");
    }, 1000);
  });
};

export default {
  name: "App",
  components: {
    Field,
    Form,
    ErrorMessage,
  },
  methods: {
    onSubmit(values) {
      alert(JSON.stringify(values, null, 2));
    },
    async validateEmail(value) {
      const result = await mockApiRequest(value);
      return result ? true : "This email is already taken";
    },
  },
};
</script>

We have the mockApiRequest function that returns a promise to simulate async validation.

We use that in the validateEmail method.

If it resolves to true , then the field is valid.

Otherwise, it’s not valid.

Then we pass the method into the rules prop to do the validation.

When we press Submit, validation will be run.

If it’s valid, then the onSubmit method will be run.

Cross-Field Validation

We can add cross-field validation with Vee-Validate 4.

For example, to check if the confirm password field matches the password field, we can write:

<template>
  <div>
    <Form @submit="onSubmit" :validation-schema="schema">
      <div>
        <label for="password">Password</label>
        <Field id="password" name="password" type="password" />
        <ErrorMessage name="password" />
      </div>

      <div>
        <label for="passwordConfirmation">Confirm Password </label>
        <Field
          id="passwordConfirmation"
          name="passwordConfirmation"
          type="password"
        />
        <ErrorMessage name="passwordConfirmation" />
      </div>

      <button type="submit">Submit</button>
    </Form>
  </div>
</template>

<script>
import { Field, Form, ErrorMessage, defineRule } from "vee-validate";
import * as yup from "yup";

defineRule("required", (value) => {
  if (!value) {
    return "This is required";
  }

  return true;
});

defineRule("min", (value, [min]) => {
  if (value && value.length < min) {
    return `Should be at least ${min} characters`;
  }

  return true;
});

defineRule("confirmed", (value, [other]) => {
  if (value !== other) {
    return `Passwords do not match`;
  }

return true;
});

export default {
  name: "App",
  components: {
    Field,
    Form,
    ErrorMessage,
  },
  data: () => {
    const schema = yup.object().shape({
      password: yup.string().min(5).required(),
      passwordConfirmation: yup
        .string()
        .required()
        .oneOf([yup.ref("password")], "Passwords do not match"),
    });

    return {
      schema,
    };
  },
  methods: {
    onSubmit(values) {
      alert(JSON.stringify(values, null, 2));
    },
  },
};
</script>

We called defineRule to define the required and min rules to check that the field has a value filled in and that it has the minimum length required.

The confirmed rule is defined with the other variable to check the value of the other field.

We define the Yup schema in the data method.

The validation rules for the passwordConfirmation rule calls oneOf with yup.ref("password") to check that the value of it matches the value of the password field.

Then we pass in the schema into the validation-schema prop.

Conclusion

We can validate form fields asynchronously and do cross-field validation in our Vue 3 app with Vee-Validate 4.

Categories
Vue 3

Form Validation in a Vue 3 App with Vee-Validate 4 — Validating Custom Inputs

Form validation is an important part of any app.

In this article, we’ll look at how to use Vee-Validate 4 in our Vue 3 app for form validation.

Validating Custom Inputs

We can use Vee-Validate 4 to validate custom input components.

For example, we can write:

components/TextInput.vue

<template>
  <div
    class="TextInput"
    :class="{ 'has-error': !!errorMessage, success: meta.valid }"
  >
    <label :for="name">{{ label }}</label>
    <input
      :name="name"
      :id="name"
      :type="type"
      :value="inputValue"
      @input="handleChange"
      @blur="handleBlur"
    />

    <p class="help-message" v-show="errorMessage || meta.valid">
      {{ errorMessage || successMessage }}
    </p>
  </div>
</template>

<script>
import { useField } from "vee-validate";

export default {
  props: {
    type: {
      type: String,
      default: "text",
    },
    value: {
      type: String,
      default: "",
    },
    name: {
      type: String,
      required: true,
    },
    label: {
      type: String,
      required: true,
    },
    successMessage: {
      type: String,
      default: "",
    },
  },
  setup(props) {
    const {
      value: inputValue,
      errorMessage,
      handleBlur,
      handleChange,
      meta,
    } = useField(props.name, undefined, {
      initialValue: props.value,
    });

    return {
      handleChange,
      handleBlur,
      errorMessage,
      inputValue,
      meta,
    };
  },
};
</script>

App.vue

<template>
  <Form :validation-schema="schema" @submit="onSubmit">
    <TextInput
      name="name"
      type="text"
      label="Full Name"
      placeholder="Your Name"
      success-message="Nice to meet you!"
    />
  </Form>
</template>

<script>
import { Form } from "vee-validate";
import TextInput from "@/components/TextInput.vue";
import * as yup from 'yup'

export default {
  components: {
    Form,
    TextInput,
  },
  data() {
    const schema = yup.object().shape({
      name: yup.string().required(),
    });

    return {
      schema,
    };
  },
  methods: {
    onSubmit(values) {
      alert(JSON.stringify(values, null, 2));
    },
  },
};
</script>

We create the TextInput component that takes some props.

name is the name attribute value of our component.

type has the type attribute value.

label has the text for the label.

placeholder has the input placeholder value.

success-message has the validation success message.

We then defined the schema in the data method and passed that into the validation-schema prop.

In TextInput.vue , we get the props and put them in the place we want.

In the setup method, we call the useField function to add validation for our field.

We pass in the name attribute value as the first argument.

And then we set the initial value with the props.value value in the 3rd argument.

It returns an object with the value property which we can use to set the value prop.

handleChange is passed into the input prop to set the inputted value.

handleBlur is used to handle the blur event.

meta has is an object with the validity state of the field.

errorMessage has the form field error message.

Now when we type in some value into the field, we should see different messages depending on if the value is valid or not.

Conclusion

We can use Vee-Validate 4 to validate Vue 3 custom input components.

Categories
Vue 3

Form Validation in a Vue 3 App with Vee-Validate 4 — Yup Schemas, Radio Buttons, and Checkboxes

Form validation is an important part of any app.

In this article, we’ll look at how to use Vee-Validate 4 in our Vue 3 app for form validation.

Yup Schemas in Data

Making our validation schema reactive is an unnecessary overhead that we can eliminate.

To do this, we can either add the schema with the setUp method or call markRaw with our schema to make it non-reactive.

For example, we can write:

<template>
  <Form v-slot="{ errors }" :validation-schema="schema">
    <Field as="input" name="email" />
    <span>{{ errors.email }}</span>
    <br />

    <Field as="input" name="password" type="password" />
    <span>{{ errors.password }}</span>
    <br />

    <button>Submit</button>
  </Form>
</template>

<script>
import { Form, Field } from "vee-validate";
import * as yup from "yup";

export default {
  components: {
    Form,
    Field,
  },
  setup() {
    const schema = yup.object().shape({
      email: yup.string().required().email(),
      password: yup.string().required().min(8),
    });

    return {
      schema,
    };
  },
};
</script>

to create our schema in the setUp method.

We just return the object returned by Yup so the schema property won’t be reactive.

To call markRaw , we write:

<template>
  <Form v-slot="{ errors }" :validation-schema="schema">
    <Field as="input" name="email" />
    <span>{{ errors.email }}</span>
    <br />

    <Field as="input" name="password" type="password" />
    <span>{{ errors.password }}</span>
    <br />

    <button>Submit</button>
  </Form>
</template>

<script>
import { markRaw } from "vue";
import { Form, Field } from "vee-validate";
import * as yup from "yup";

export default {
  components: {
    Form,
    Field,
  },
  data() {
    const schema = markRaw(
      yup.object().shape({
        email: yup.string().required().email(),
        password: yup.string().required().min(8),
      })
    );

    return {
      schema,
    };
  },
};
</script>

We call markRaw with our Yup schema to make it non-reactive.

Radio Buttons

We can use Vee-Validate 4 to validate radio buttons.

For example, we can write:

<template>
  <Form :validation-schema="schema" @submit="onSubmit">
    <Field name="drink" type="radio" value=""></Field> None
    <Field name="drink" type="radio" value="Tea"></Field> Tea
    <Field name="drink" type="radio" value="Coffee"></Field> Coffee
    <ErrorMessage name="drink" />
  </Form>
</template>

<script>
import { Form, Field, ErrorMessage } from "vee-validate";

export default {
  components: {
    Form,
    Field,
    ErrorMessage,
  },
  data() {
    return {
      schema: {
        drink: (value) => {
          if (value) {
            return true;
          }

          return "You must choose a drink";
        },
      },
    };
  },
  methods: {
    onSubmit(values) {
      alert(JSON.stringify(values, null, 2));
    },
  },
};
</script>

We have the Field component with type set to radio to create our radio buttons.

Then in the data method, we return the schema reactive property with the drink method to validate our radio buttons.

value has the checked value as specified by the value prop.

We return true if the choices are valid and an error message otherwise.

And then we display the ErrorMessage component to show error messages.

Checkboxes

We can add checkbox validation with Vee-Validate 4.

For instance, we can write:

<template>
  <Form :validation-schema="schema" @submit="onSubmit">
    <Field name="drink" type="checkbox" value="Milk"></Field> Milk
    <Field name="drink" type="checkbox" value="Tea"></Field> Tea
    <Field name="drink" type="checkbox" value="Coffee"></Field> Coffee
    <ErrorMessage name="drink" />
  </Form>
</template>

<script>
import { Form, Field, ErrorMessage } from "vee-validate";

export default {
  components: {
    Form,
    Field,
    ErrorMessage,
  },
  data() {
    return {
      schema: {
        drink: (value) => {
          if (Array.isArray(value) && value.length) {
            return true;
          }
          return "You must choose a drink";
        },
      },
    };
  },
  methods: {
    onSubmit(values) {
      alert(JSON.stringify(values, null, 2));
    },
  },
};
</script>

to add validation for checkboxes. We set the type prop to checkbox to render a checkbox.

And the drink method has all the checked values in an array if any checkbox is checked.

We return true if the choices are valid and an error message otherwise.

And then we display the ErrorMessage component to show error messages.

Conclusion

We can make Yup schemas non-reactive to reduce overhead and validate radio buttons and checkboxes with Vee-Validate 4 in our Vue 3 app.

Categories
Vue 3

Form Validation in a Vue 3 App with Vee-Validate 4 — Dynamic Forms and App Bundle Size

Form validation is an important part of any app.

In this article, we’ll look at how to use Vee-Validate 4 in our Vue 3 app for form validation.

Dynamic Form with Initial Values

We can set initial values for dynamic forms as we do with static forms.

To do this, we pass in an object with the values into the initial-values prop:

<template>
  <Form
    v-slot="{ errors }"
    :validation-schema="schema.validation"
    :initial-values="schema.values"
  >
    <div v-for="field in schema.fields" :key="field.name">
      <label :for="field.name">{{ field.label }}</label>
      <Field :as="field.as" :id="field.name" :name="field.name" />
      <span>{{ errors[field.name] }}</span>
    </div>

    <button>Submit</button>
  </Form>
</template>

<script>
import { Form, Field } from "vee-validate";
import * as yup from "yup";

export default {
  name: "DynamicForm",
  components: {
    Form,
    Field,
  },
  data() {
    return {
      schema: {
        fields: [
          {
            label: "Name",
            name: "name",
            as: "input",
          },
          {
            label: "Email",
            name: "email",
            as: "input",
          },
          {
            label: "Password",
            name: "password",
            as: "input",
          },
        ],
        validation: yup.object().shape({
          email: yup.string().email().required(),
          name: yup.string().required(),
          password: yup.string().min(8).required(),
        }),
        values: {
          name: "james",
          email: "abc@abc.com",
          password: "password",
        },
      },
    };
  },
};
</script>

We add the values property with the values into the schema reactive property.

Then we pass that into the validation-schema prop.

The property names of schema.values should match the values of the name prop of the field we want to fill in.

Now we should see the initial form values populated.

Reducing App Bundle Size

To reduce the bundle size of our app, we can selectively import the parts of the yup module we require.

For example, instead of writing:

<template>
  <Form
    v-slot="{ errors }"
    :validation-schema="schema.validation"
    :initial-values="schema.values"
  >
    <div v-for="field in schema.fields" :key="field.name">
      <label :for="field.name">{{ field.label }}</label>
      <Field :as="field.as" :id="field.name" :name="field.name" />
      <span>{{ errors[field.name] }}</span>
    </div>

    <button>Submit</button>
  </Form>
</template>

<script>
import { Form, Field } from "vee-validate";
import * as yup from "yup";

export default {
  name: "DynamicForm",
  components: {
    Form,
    Field,
  },
  data() {
    return {
      schema: {
        fields: [
          {
            label: "Name",
            name: "name",
            as: "input",
          },
          {
            label: "Email",
            name: "email",
            as: "input",
          },
          {
            label: "Password",
            name: "password",
            as: "input",
          },
        ],
        validation: yup.object().shape({
          email: yup.string().email().required(),
          name: yup.string().required(),
          password: yup.string().min(8).required(),
        }),
      },
    };
  },
};
</script>

We can write:

<template>
  <Form
    v-slot="{ errors }"
    :validation-schema="schema.validation"
    :initial-values="schema.values"
  >
    <div v-for="field in schema.fields" :key="field.name">
      <label :for="field.name">{{ field.label }}</label>
      <Field :as="field.as" :id="field.name" :name="field.name" />
      <span>{{ errors[field.name] }}</span>
    </div>

    <button>Submit</button>
  </Form>
</template>

<script>
import { Form, Field } from "vee-validate";
import { object, string } from "yup";

export default {
  name: "DynamicForm",
  components: {
    Form,
    Field,
  },
  data() {
    return {
      schema: {
        fields: [
          {
            label: "Name",
            name: "name",
            as: "input",
          },
          {
            label: "Email",
            name: "email",
            as: "input",
          },
          {
            label: "Password",
            name: "password",
            as: "input",
          },
        ],
        validation: object().shape({
          email: string().email().required(),
          name: string().required(),
          password: string().min(8).required(),
        }),
      },
    };
  },
};
</script>

We just import the object and string methods form the yup module, then we call them to create our schema.

Conclusion

We can add dynamic forms with initial values easily into our Vue 3 dynamic forms with Vee-Validate 4.

Also, we can reduce bundle size by selectively importing items from the yup module.

Categories
Vue 3

Form Validation in a Vue 3 App with Vee-Validate 4 — useForm and Dynamic Forms

Form validation is an important part of any app.

In this article, we’ll look at how to use Vee-Validate 4 in our Vue 3 app for form validation.

useForm()

The useForm() function lets us create form context that we can use to create reactive forms.

To use it, we write:

<template>
  <form @submit="onSubmit">
    <Field name="email" />
    <span>{{ errors.email }}</span>
    <br />

    <Field name="password" />
    <span>{{ errors.password }}</span>
    <br />

    <input type="submit" />
  </form>
</template>

<script>
import { Field, useForm } from "vee-validate";
import * as yup from "yup";

export default {
  components: {
    Field,
  },
  setup() {
    const { handleSubmit, errors } = useForm({
      validationSchema: yup.object().shape({
        email: yup.string().required().email(),
        password: yup.string().required().min(3),
      }),
    });
    const onSubmit = handleSubmit((values, actions) => {
      alert(JSON.stringify(values, null, 2));
      actions.resetForm();
    });

    return {
      errors,
      onSubmit,
    };
  },
};
</script>

We call the useForm function with the validationSchema property to set the validation schema.

email and password are the name attribute values of the fields.

It returns an object with the handleSubmit function and the errors object with the form validation errors.

We use errors in the form.

And we call handleSubmit with a callback to handle submissions.

values has the inputted values.

actions has the resetForm method to reset the validation status of the form.

We can set the initial values of the form fields.

To do this, we write:

<template>
  <form @submit="onSubmit">
    <Field name="email" />
    <span>{{ errors.email }}</span>
    <br />

    <Field name="password" />
    <span>{{ errors.password }}</span>
    <br />

    <input type="submit" />
  </form>
</template>

<script>
import { Field, useForm } from "vee-validate";
import * as yup from "yup";

export default {
  components: {
    Field,
  },
  setup() {
    const { handleSubmit, errors } = useForm({
      initialValues: {
        email: "abc@abc.com",
        password: "",
      },
      validationSchema: yup.object().shape({
        email: yup.string().required().email(),
        password: yup.string().required().min(3),
      }),
    });
    const onSubmit = handleSubmit((values, actions) => {
      alert(JSON.stringify(values, null, 2));
      actions.resetForm();
    });

    return {
      errors,
      onSubmit,
    };
  },
};
</script>

to add the initialValues property into the object we pass into useForm to set the value of the email field.

Dynamic Form

We can use Vee-Validate 4 to validate dynamically rendered forms.

For example, we can write:

<template>
  <Form v-slot="{ errors }" :validation-schema="schema.validation">
    <div v-for="field in schema.fields" :key="field.name">
      <label :for="field.name">{{ field.label }}</label>
      <Field :as="field.as" :id="field.name" :name="field.name" />
      <span>{{ errors[field.name] }}</span>
    </div>

    <button>Submit</button>
  </Form>
</template>

<script>
import { Form, Field } from "vee-validate";
import * as yup from "yup";

export default {
  components: {
    Form,
    Field,
  },
  data() {
    return {
      schema: {
        fields: [
          {
            label: "Name",
            name: "name",
            as: "input",
          },
          {
            label: "Email",
            name: "email",
            as: "input",
          },
          {
            label: "Password",
            name: "password",
            as: "input",
          },
        ],
        validation: yup.object().shape({
          email: yup.string().email().required(),
          name: yup.string().required(),
          password: yup.string().min(8).required(),
        }),
      },
    };
  },
};
</script>

In the data method, we return an object with the schema reactive property.

It has the data to render the fields in the fields array.

We use it to render the label and name and what to render the Field as with the as property.

The validation property has the validation schema for our fields.

In the template, we render the items and display the errors in the span .

This is more convenient than adding each field into our template.

Conclusion

We can create forms with the useForm function.

And we can create dynamic forms with validation in our Vue 3 app with Vee-Validate 4.