Image

validating-vue-3-forms

8 min read
Last update: December 19, 2021

Setting Up

In this lesson, we’re going to get started with the vee-validate library and validate our first form fields!

Getting started

If you haven’t done it already, please grab a copy of the course’s repository from the Lesson Resources below. I’ve set us up with a sample login form that includes an email and a password field.

For the sake of continuity, we’re using the components we created in Vue Mastery’s Vue 3 Forms course, but you can use your own form components if you prefer. The details of how the component works are not relevant for this course. It just needs to be able to v-model its state to its parent. Also it would be a plus if it is able to display labels and error messages.

With that out of the way, let’s get set up by adding vee-validate into our project.

Run the following command in your terminal:

yarn add vee-validate@next
# or
npm i vee-validate@next --save

There are two ways we can use vee-validate in Vue 3. Through a template-based component that is provided to us by the library called Field, or through the use of composition API.

The component approach is the simplest way of using vee-validate, but it does require us to use their pre-bundled input component. Since we want to leverage our custom-made components and have as much control over our form as we can, we are going to use the Composition API approach. The library exposes a few intuitive composition functions that allow us to define which pieces of data on our state we are going to use as fields, which need to be validated.

If you have never used the Composition API before with Vue 3, this would be a good moment to take a look at the Vue 3 Composition API course here on Vue Mastery before the next lesson.


Validating our email input

Let’s head over to LoginForm.vue and start working on validating our email input. Notice that we already have a setup method where we are declaring an onSubmit method, so that our form’s @submit event has something tied to it.

📃 LoginForm.vue

<template>
    <form @submit.prevent="onSubmit">
        <BaseInput label="Email" type="email" />

        <BaseInput label="Password" type="password" />

        <BaseButton type="submit" class="-fill-gradient">
            Submit
        </BaseButton>
    </form>
</template>

<script>
    export default {
        setup() {
            function onSubmit() {
                alert('Submitted')
            }

            return {
                onSubmit
            }
        }
    }
</script>

Let’s start by importing the composition function useField from vee-validate.

📃 LoginForm.vue

import { useField } from 'vee-validate'
export default {
...
}

The useField function tells vee-validate that we are creating a form field that we want to have validated. In its simplest form, it accepts two parameters:

  1. A String with the name of the model for this field. In our case, we will simply call it email.
  2. A function to check if the value of the field is valid or not.

Let’s use this function to set up validation for our email input.

📃 LoginForm.vue

setup () {
[...]

const email = useField('email', function(value) {
if (!value) return 'This field is required'

return true
})

return {...}
}

We have set the name email for our model of this input, and we have created a new function that will validate the email input every time it updates.

Notice the form is receiving a value param. We then check if this value is empty, and if it is, we return a String with an error message.

Validation functions in vee-validate can return either true to indicate that the field is valid, or a string that will represent the error message we will display for the user.

Let’s take this a bit further, and use a regex to check that the value is a valid email address.

📃 LoginForm.vue

setup () {
[...]

const email = useField('email', function(value) {
if (!value) return 'This field is required'

const regex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
        if (!regex.test(String(value).toLowerCase())) {
        return 'Please enter a valid email address'
        }

        return true
        })

        return {...}
        }
        ```

        Here we are checking to see if the `value` that we are getting from the email field passes the regex `test`. If it doesn’t, we return a different string with an error that clarifies what we want from the user.

        This gives us the flexibility to do all types of checks and validations here for our fields, and return a different error message for each one.

        Notice that we are currently saving the result from the `useField` method in an `email` constant.

        This `email` constant is an object that contains two very important properties.

        The first one, `value`, is a Vue `ref` or reactive value. We will use this value to keep the data-binding between our input component and the state.

        Let’s use this `value` property of the `email` object to `v-model` bind our input in our template.

        First, we will `return` the `email.value` property from our `setup` function so that we can use it in our template.

        **📃 LoginForm.vue**

        ```jsx
        const email = useField(...)

        return {
        onSubmit,
        email: email.value
        }
        ```

        We are exporting this value property as `email` so that it is clearer in our template what is happening with the binding.

        We can now go to the `template` of our .vue file and add a `v-model` binding to our `BaseInput` for the `email` field, and bind it to the `email` ref we just exported in `setup`.

        **📃 LoginForm.vue**

        ```html
        <template>
            [...]
            <BaseInput label="Email" type="email" v-model="email" />
            [...]
        </template>
        ```

        The second important property that the `useField` function gives us is the `errorMessage` property.

        Whenever our validation function fails and returns an error message, it will be stored in this property of the `email` constant. Let’s return this property as well in our `setup` function, and bind it to the `:error` prop of our `BaseInput` component.

        **📃 LoginForm.vue**

        ```jsx
        const email = useField(...)

        return {
        onSubmit,
        email: email.value,
        emailError: email.errorMessage
        }
        ```

        Now, let’s go to the template and bind the `emailError` we just exported to our `BaseInput`'s `error` prop.

        ```html
        <template>
            [...]
            <BaseInput label="Email" type="email" v-model="email" :error="emailError" />
            [...]
        </template>
        ```

        If we check this out in the browser, we’ll see that as we start typing, the validation fires. Since we’re not entering a valid email address, the `emailError` property is set, and our error message is displayed!

        ![https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2Femailerror.opt.jpg?alt=media&token=56701911-6048-4719-9122-156a877ab091](https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2Femailerror.opt.jpg?alt=media&token=56701911-6048-4719-9122-156a877ab091)

        ---

        ## Cleaning up

        This is working great so far, but I want to do a bit of a cleanup with how we are setting up our `email` constant, and briefly talk about JavaScript destructuring.

        Notice how the `useField`method returns an object. In this object there are quite a few properties, and we’re capturing them all (as a full object) inside the `email` constant.

        ```jsx
        const email = useField(...)
        ```

        Then, we go ahead and access those properties of the `email` object in our return statement, and pass return them like so:

        ```jsx
        return {
        onSubmit,
        email: email.value,
        emailError: email.errorMessage
        }
        ```

        This perfectly ok to do, and this verboseness can sometimes lead to code clarity, which is always a good thing. But often when working with the Composition API you will notice that most resources will make use of JavaScript destructuring.

        Destructuring in itself is beyond the scope of this course, but MDN has [a great article](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) that explains it in detail.

        The previous example, written using JavaScript destructuring, looks like this:

        ```jsx
        const { value, errorMessage } = useField(...)

        return {
        onSubmit,
        email: value,
        emailError: errorMessage
        }
        ```

        The property `value` and `errorMessage` of the object returned by `useField` are now readily available to us as variables, so we can pass them directly into the `return` statement.

        ---

        There’s one last bit I want to show you before we move on. There are times we will need to rename these properties for clarity, or because they may conflict with one another. In JavaScript, we can rename a destructured property by adding a `:` colon after the property in the destructured syntax, and the new name.

        Let me show you a working example. Let’s rename the `value` to `email`, and the `errorMessage` to `emailError`.

        ```jsx
        const { value: email, errorMessage: emailError } = useField(...)

        return {
        onSubmit,
        email: email,
        emailError: emailError
        }
        ```

        Both syntaxes are completely fine, so feel free to use whichever makes more sense to you with our examples.

        ---

        ## Wrapping up

        Now that we’ve successfully validated our first form component, we are ready to move on to the next lesson and look at defining our validations at form-level using a schema.