CODEPANION BLOG

Implement form validation from scratch using Vue.js mixins

Learn how to implement form validation in Vue.js without using any validation library.

I will not repeat myself.

I will not repeat myself.

I will not repeat myself.

I will not repeat myself.

Vue Mixins!

Great way to reuse functionalities between Vue components. Mixin is just another object that allows us to add options just like in any other component. We can include mixin into components and get access to all the options from the mixin.

So, mixin can contain methods, computed, data, lifecycle hooks… almost everything like any regular Vuejs component except it cannot contain components. It would be a bad idea. :)

After we include mixin in Vuejs components, component’s options and mixin’s options will be merged so the options from the mixin will be available in component.

I use mixins very often when I have a lot of validation rules that I need to reuse across many form components on different pages.

If you want to build your input validation logic from scratch, using mixins can be a great way for doing that.

Create a folder with a name mixins, where you will put all mixins files inside.

Let’s create validationMixin.js mixin:

// mixins/validationMixin.js

const validationMixin = {
  methods: {
    // add methods
  },
  data: () => ({
    // add data
  })
}

export default validationMixin;

Now import the mixin inside the component in which you want to use mixin's options. In this example, we will create a component with the form and the input fields. Also, we will add the error object and the user object. We will put error messages for each input field in the error object. User object’ properties will be bind with the form input.

<template>
  <form>
    <div class="form-element">
      <label>
        Nickname
      </label>
      <input
        v-model="user.nickname" 
        name="nickname"
        type="text" />
    </div>

    <div class="form-element">
      <label>
        Email
      </label>
      <input
        v-model="user.email" 
        name="email"
        type="text" />
    </div>

    <button type="submit">
      Submit
    </button>
  </form>
</template>

<script>
import validationMixin from './../mixins/validations';

export default {
  name: 'UserForm',
  data: () => ({
    errors: [],
    user: {
      nickname: '',
      email: ''
    }
  }),
  mixins: [validationMixin]
}
</script>

It is a very common situation where you have multiple forms with the input fields with the same validation rules. So it is a good practice to keep all validation rules in one place and avoid repeating your code.

So, let's start adding validation rules.

// mixins/validationMixin.js
const EMAIL_REGEX = /\S+@\S+\.\S+/;

...
data: () => ({
  validationRules: {
    nickname: {
      rules: [
        value => !!value || 'Nickname is required',
        value => (value.length <= 12) || 'Nickname must be less than 12 characters'
      ]
    },
    email: {
      rules: [
        value => EMAIL_REGEX.test(value) || 'Please enter a valid email address'
      ]
    }
  }
})
...

Create validation rules as an array of functions that returns true or a validation message. Each function is a validation rule with the input value as a parameter. Nickname field has two rules (required and maximum character length), the email field has regex validation with the test method which searches for a match between a regular expression and the email in this case. Now the validation rules are accessible in the UserForm component.

We also need to add methods to validate the form or the single input field.

// mixins/validationMixin.js
...
methods: {
  validateField(inputName, value) {
    return this.validationRules[inputName].rules
      .filter(rule => {
        const isValid = rule(value);

        if(isValid !== true) {
          return isValid;
        }
      })
      .map(rule => rule(value))
  },
  validateForm(form) {
    const formErrors = {};
    let formIsValid = true;

    for(let property in form) {
      const errors = this.validateField(property, form[property]);

      if(errors.length) {
        formIsValid = false;
      }

      formErrors[property] = errors;
    }

    formErrors.formIsValid = formIsValid;

    return formErrors;
  }
},
...

We have two methods. validateField and validateForm.

validateField method has two parameters (input name and input value). Input name is used to get validation rules from validation rules object and the input value is used for rule method to examine if the value passes validation.

validateForm is used to validate all the input fields’ values. This method iterate through all data properties and validate each property. This method returns validation error messages for all invalid fields. We also have formIsValid parameter to indicate if the form is valid or not.

Now inside the component, we can use our validation rules and methods.

// components/UserForm.vue
methods: {
  onSubmit(e) {
    e.preventDefault();
    this.errors = this.validateForm(this.user);

    if(this.errors.formIsValid) {
      console.log('Form is valid. Make an ajax request');
    }
  },
  onChange(e, inputName) {
    const inputValue = e.target.value;
    // or
    // const inputValue = this.user[inputName];
    const inputErrors = this.validateField(inputName, inputValue);

    if(inputErrors && inputErrors.length) {
      this.errors[inputName] = inputErrors;
    } else {
      this.errors[inputName] = null;
    }
  }
}

And the template should look like this:

<template>
  <form @submit="onSubmit">
     <div class="form-element">
       <label
         :class="{ 'error': errors.nickname && errors.nickname.length }">
         Nickname
       </label>
       <input
         @input="onChange($event, 'nickname')"
         :class="{ 'error-input': errors.nickname && errors.nickname.length }"
         v-model="user.nickname"
         type="text" />
       <ul v-if="errors.nickname && errors.nickname.length">
         <li class="error" v-for="(error, i) in errors.nickname" :key="i">
           {{ error }}
         </li>
       </ul>
     </div>
    
     <div class="form-element">
       <label
         :class="{ 'error': errors.email && errors.email.length }">
         Email
       </label>
       <input
         @input="onChange($event, 'email')"
         :class="{ 'error-input': errors.email && errors.email.length }"
         v-model="user.email"
         type="text" />
       <ul v-if="errors.email && errors.email.length">
         <li class="error" v-for="(error, i) in errors.email" :key="i">
           {{ error }}
         </li>
       </ul>
     </div>

     <button type="submit">
       Submit
     </button>
   </form>
</template>

Now, we display validation error and bind error class if the field is invalid.

©2020 Stefan Mitrović