Vuelidate and Vuelidate-error-extractor.

Easy form validation

Dobromir Hristov

Lead FE developer @Hypefactors

       Bulgaria

Team Member

Vue

  1. Validation without Plugins
  2. Validation with Vuelidate
  3. Custom Vuelidate Validators
  4. Large forms validation
  5. Validation message display
  6. Vuelidate-error-extractor

Agenda

 without plugins

Validation

data: () => ({
    form: {
      name: "",
      email: ""
    },
    dirty: false
}),
computed: {
  hasErrors() {
    return !(this.isNameValid && this.isEmailValid);
  },
  isNameValid() {
    const name = this.form.name
    return required(name) && min(name, 10);
  },
  isEmailValid() {
    const email = this.form.email
    return required(email) && isEmail(email);
  }
},
methods: {
  submit() {
    this.dirty = true;
    // return if errors
    if (this.hasErrors) {
      return false;
    }
    alert("Form submitted");
  }
}
<div 
    class="px-4" 
    :class="{ hasError: !isNameValid && dirty }"
>
  <label class="mr-2 font-bold text-grey">Name</label>
  <input type="text" class="input" v-model="form.name" />
</div>
<div 
    class="px-4" 
    :class="{ hasError: !isEmailValid && dirty }"
>
  <label class="mr-2 font-bold text-grey">Email</label>
  <input type="email" class="input" v-model="form.email" />
</div>

Outcome

  1. Simple for small forms
  2. Computed props auto update the validations
  3. Can extract common validators
  1. Need to define computed props for every field
  2. Need to update on multiple places
  3. Need to keep track of dirty fields
  4. Error prone

Cons

Pros

using Vuelidate

Validation

  1. Data-model oriented
  2. Rules resemble data structure
  3. Easy to understand
  4. Allow dynamic rule set
  5. Allow Async rules
  6. Track dirty fields
  7. Has many prebuilt validators
import { required, email } from 'vuelidate/lib/validators'

export default {
  data:()=>({
    form: {
      name: '',
      email: ''
    }
  }),
  validations: {
    form: {
      name: { required },
      email: { required, email }
    }
  }
}

Outcome

  1. Less boilerplate
  2. Less repetition
  3. Less user errors
  4. Automatic dirty field tracking

Vuelidate validators

Custom

  1. Wide variety of prebuilt validators
  2. Custom validators are just functions that return Boolean or Promise<Boolean>
  3. Receive Value and Context as params
  • required
  • requiredIf
  • requiredUnless
  • minLength
  • maxLength
  • minValue
  • maxValue
  • between
  • alpha
  • alphaNum
  • numeric
  • integer
  • decimal
  • email
  • ipAddress
  • macAddres
  • sameAs
  • url

Custom Validator

 Example

export function isNotJoe(value) {
  if (!value) return true;
  return !value.includes("Joe");
}

export function notGmail(value = "") {
  return !value || !value.includes("gmail")
}

export function illegalChars(value) {
  const charsList = ["#", "&", "$", "~"];
  const regex = `[${charsList.join(",")}]`;
  const regexExp = new RegExp(regex);
  return !regexExp.test(value);
}
import { illegalChars, isNotJoe, notGmail } from './validators'
import { required, email } form 'vuelidate/lib/validators'

export default {
    data: () => ({
        form: {
            name: '',
            email: ''
        }
    }),
    validations: {
        form: {
            name: { required, illegalChars, isNotJoe },
            email: { required, email, notGmail }
        }
    }
}

Usage

form validation

Large

Approach

  1. Split logical parts into sub components
  2. Keep data on the parent
  3. Pass only required data to children
  4. Children emit changes to parent
  5. Parent passes validator down to children
  6. Parent is responsible for submitting form

Example Child

<template>
  <div 
    class="form-group" 
    :class="{ hasError: v.$error }"
  >
    <label class="mr-2 font-bold text-grey">
      Email
    </label>
    <input
      type="email"
      class="input"
      v-model="email"
      placeholder="user@yahoo.com"
    />
    <div class="text-sm mt-2 text-grey-darker">
      Email is required and must NOT be gmail<br />
      Async check if email is less than 10 chars
    </div>
  </div>
</template>

<script>
export default {
  props: {
    value: {
      type: String,
      default: ""
    },
    v: {
      type: Object,
      required: true
    }
  },
  computed: {
    email: {
      get() {
        return this.value;
      },
      set(value) {
        this.v.$touch();
        this.$emit("input", value);
      }
    }
  }
};
</script>

Child Usage

<form @submit.prevent="submit" novalidate>
    <div class="flex justify-center my-6">
        <div class="px-4">
            <name-component v-model="form.name" :v="$v.form.name"/>
        </div>
        <div class="px-4">
            <email-component v-model="form.email" :v="$v.form.email"/>
        </div>
    </div>
    <div class="text-center">
        <button type="submit" class="button">
            Submit
        </button>
    </div>
</form>

message  display

Error

  • Good for UX
  • Immediate feedback

Pros

  • Repetitive
  • Error prone
  • Time consuming
  • Extra noise in templates

Cons

<div class="form-group" :class="{ 'hasError': v.$error }">
  <label class="mr-2 font-bold text-grey">Email</label>
  <input 
    type="email"
    class="input"
    v-model="email" 
    placeholder="user@yahoo.com"
    @input="v.$touch()"
  >
  <div class="text-sm mt-2 text-red" v-if="v.$error">
    <div v-if="!v.required">Email is required</div>
    <div v-if="!v.notGmail">Email should not be a Gmail one</div>
    <div v-if="!v.isEmailAvailable">Email is not available</div>
    <div v-if="!v.email">Email is not a properly formatted</div>
  </div>
</div>

Error

messages example

to the rescue

Vuelidate-error-extractor

<form-group :validator="v" label="Email">
  <input 
    type="email"
    class="input"
    v-model="email"
    placeholder="user@yahoo.com"
    @input="v.$touch()">
</form-group>
<div class="form-group" :class="{ 'hasError': v.$error }">
  <label class="mr-2 font-bold text-grey">Email</label>
  <input 
    type="email"
    class="input"
    v-model="email" 
    placeholder="user@yahoo.com"
    @input="v.$touch()"
  >
  <div class="text-sm mt-2 text-red" v-if="v.$error">
    <div v-if="!v.required">Email is required</div>
    <div v-if="!v.notGmail">Email should not be a Gmail one</div>
    <div v-if="!v.isEmailAvailable">Email is not available</div>
    <div v-if="!v.email">Email is not a properly formatted</div>
  </div>
</div>

After

Before

Usage

import VuelidateErrorExtractor, { templates } from "vuelidate-error-extractor";

Vue.use(VuelidateErrorExtractor, {
  i18n: false,
  // Define common validation messages.
  messages: {
    required: "{attribute} is required!",
    isJoe: "{attribute} must be Joe",
    notGmail: "{attribute} must not be gmail",
    email: "{attribute} is not a valid Email address.",
    isEmailAvailable:
      "{attribute} is not available. Must be at least 10 characters long."
  }
});

Vue.component("form-group", templates.singleErrorExtractor.foundation6);

Installation

  • Templates for Foundation, Bootstrap 3 & 4
  • Predefine common reusable error messages
  • Has support for Vue-i18n
  • Removes repetitive work
  • Auto extracts all the errors
  • Obeys dirty rule check
  • Simple to use
  • Extendable

Pros

error component

Custom

<template>
  <div class="form-group" :class="{ hasError: hasErrors, hasSuccess: isValid }">
    <div class="label">
      {{ label }}
    </div>
    <div class="control"><slot/></div>
    <div class="control-helper text-red mt-4 text-sm" v-if="hasErrors">
      <div v-for="error in activeErrorMessages" :key="error">{{ error }}</div>
    </div>
  </div>  
</template>
<script>
import { singleErrorExtractorMixin } from "vuelidate-error-extractor";

export default {
  mixins: [singleErrorExtractorMixin]
};
</script>
errors Object All validation error objects for the field. Its a dynamic property so its in sync with the form field's state.
activeErrors Object The currently active error objects.
activeErrorMessages Object The currently active error messages.
mergedMessages Object Merges both the messages prop and the globally defined ones on init.
firstError Object Returns the first error object.
firstErrorMessage String Returns the first active error message.
hasErrors Boolean If field has any errors.

Available Properties

Usage

  • ElementUI
  • iView
  • Framework7
  • Vuetify
  • MintUI
  • MuseUI
  • Quasar
  • Vue Material
  • BootstrapVue
  • Buefy
  • Vuesax

with UI Frameworks

error component

Summary

Usage & Features

  • Extendable 
  • Can work with deeply nested validators
  • Assigns fields names to error messages
  • Can work with $each validator
  • Bootstrap and Foundation templates included
  <form-summary :validator="$v.form"/>

Find me at

dobromir-hristov

d_m_hristov

dobromir_hristov

dobromir-hristov

Dobromir Hristov

You

Thank

Made with Slides.com