question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

VeeValidate v3.0 🚀

See original GitHub issue

VeeValidate 3.0

This is a draft implementation for version 3.0, which is mostly breaking in many ways but we will try to make it easier to migrate. The changes mostly affect directive usage and will have minimal effects on validation providers.

This document details all the changes done in v3.

You can follow the implementation PR here #2153

You can test the v3 release with the vee-validate-edge npm package.

Goals

The goals of this release are as following:

  • Rewrite in TypeScript.
  • v-validate directive deprecation.
  • Overhaul the custom-rules/extension API.
  • Overhaul the localization API.

TypeScript

VeeValidate has been TypeScript-friendly for a very long time, but it had its shortcomings. Since it was maintained by contributors - a big shout out to you all - it often wasn’t in sync with the actual code. Also, some typings were confusing because they were intended to be internal.

A TypeScript codebase will not only give us more confidence, but it will also communicate the intent of this library APIs clearly to TypeScript/JavaScript users.

Some aspects of vee-validate are impossible to Typecheck properly like the injected errors and fields. This is touched upon later.

Directive Deprecation

This is the biggest breaking change and will certainly have some backlash against it, the directive, after all, has been the primary way to use vee-validate since x.0 releases. Even vee-validate name is a pun for the v-validate directive.

But to list the directive problems:

  • Requires global installation, tightly coupled to the rest of the library.
  • Directives do not have props, so to pass stuff around we use data-vv-* attributes which aren’t reactive, are repetitive and will add clutter your template.
  • Forces injected state errors and fields to be present.
  • v-if and v-for caveats which aren’t apparent to the developer at first glance.
  • Validation Scope is limited within a single component context, it doesn’t carry the scope to nested and internal components.
  • Complicated grouping with the Scopes API which is often confused with Validation Scope, also adds clutter to your template.
  • Probably the only library that advertises the use of the Provider/Inject API which is not recommended for public use.
  • Templates can get out of hand and noisy really fast and that makes it harder to debug/maintain.

Now a little bit of history, why was the directive the default design choice for this library?

When vee-validate first got released for Vue 1.x the directives was more powerful back then. They had their own props, and they had their own state. Which means the directive was more like a pseudo-component, it was a great choice for this library’s API because it did the job perfectly.

When Vue 2.0 got released, it removed props and this from directives, I believe they were too powerful and a distinction had to be made between them and components, while directives offered a lower level access to the DOM it meant that they had to do less, yet I was stubborn and the API was left as is. I needed to implement external state management and resort to data-vv-* attributes. It wasn’t very clean but it did the job again so the API remained the same, and because we had no better way of doing template based validation, it was fine.

Then scoped-slots came out, and it took a while for me to properly understand how they can be used to run validation, remember what I said about directives? well, components while “high-level”, they offer “low-level” access to Vue’s virtual DOM. Which is what I needed exactly and what was missing for the directive.

Now I’m going to talk about the biggest problems with the directive.

Injected State

The need for the injected state is caused by directives being stateless, as they don’t have this and no longer can store stuff on their own. This change was introduced in Vue 2.0 release which was the biggest limitation introduced to directives.

The primary way to use v-validate directive is to get your errors off an injected global state called errors, this causes a problem for maintenance sake as it is not immediately clear where did errors come from, which is the same downsides for using global mixins. Your teammates simply had to know that the project is using vee-validate.

Being a globally present state, it has poor performance as it changes frequently depending on how many fields are reading/writing to that state. This can be observed if you have 100+ fields on the same page, which while is very rare it still meant that by default vee-validate wasn’t performant.

they are impossible to type check properly without breaking somebody’s code out there.

That leaves us with one option: Use mapState like a helper to explicitly inject state, very much like vuex. I originally intended for the directive to be in v3 and introduced a full-rewrite that implements that. But the API was unwieldy in my opinion and it didn’t solve the performance issue, in fact, it made it worse.

v-if and v-for caveats

The directive life-cycle methods work most of the time, but when Vue decides to re-use a field to save up rendering time, The directive doesn’t get notified in any way. Meaning if the directive was to pick up changes in the template it had to re-check the field it is being attached to every single time before validation, which degrades performance even further as observed in the test implementation I talked about earlier.

Directives weren’t meant to be dependent on the identity of the element they are being attached to, they should be dependent solely the expression and the directive args/modifiers configured for it. Which is what was missing for my initial understanding of directives.

Sharing state between components

Using inject API for sharing state between a parent component and a child component being validated wasn’t very intuitive, also the confirmed rule and any cross-field rule would never work across components as they rely on the refs present in the context of the v-validate usage.

This problem comes up weekly, and while we could argue the docs can do a better job to describe this issue, I think by trying to address many caveats it means that the API isn’t that good in the first place.

Scope API

the data-vv-scope API was just ugly, confusing and was redundant most of the time. I personally never used it, and my team at Baianat rarely did and it rarely comes up in issues, which is an indication that it is underused, that means developers did not have multiple forms in the same component that often.

Aside from looks, it also had a hand in performance degradation, because while maybe.field can be a name for a field called literally “maybe.field” or it could be a field scoped within a “maybe” scope name. That means that every time errors.first and errors.has are used, a lot of computation has to be done to determine which case is it. Another smell for bad API.

And since directives do not have access to the rendering phase, it meant that they had no way to prepare some stuff beforehand. For example, to access field flags you need to always check for the field existence first:

fields.name && fields.name.valid

Which is annoying in large forms but there is no way around that, I tried playing around with Proxies, but they cannot be poly-filled and messed up big time with Vue.js dev-tools.

The validator API was a mess

There were actually two validator classes present, one injected in each component and the other would be the one you normally import from vee-validate and the two had identical public API but it was a mess. for example, validate and validateAll did the exact same thing. validateScopes was just an added confusion, another smell for a bad API.

The alternative

One of the goals of this release is to promote better practices, that meant less magic. So the directive had to go since it inherently promoted a few practices that are considered evil. You have been probably using vee-validate for your production site and it certainly did the job well, but did it feel elegant? There are two ways to make a simple API, it can be simple but crude or it can be simple as in elegant. I strive for the latter and I’m sure we all do.

For the last few months, I have been advertising the use of ValidationProvider and ValidationObserver components recently whenever anyone faces one of the above problems. They will be the primary way to use v-validate as of the time of this writing. The other way that can be introduced in the future if Vue.js adds new APIs to be built upon like the function API if it made sense.

At first, glance, using the ValidationProvider is more verbose, but when refactored properly they are much more productive and flexible than the directive. Also, the component API is very powerful and can allow some “magic” to happen. For example, the ValidationObserver can pick up ValidationProvider instances no matter how deeply nested they are and even if they are being used internally by other components. That means the Observer is able to correctly represent the state of forms.

Since Observers can be nested, that solved the problem of scoping, as you can group fields together by observers and it is much more cleaner and clearer in the template than the old data-vv-scope API.

They are superior in every way to the directive and having two APIs means the documentation would be splintered and one would have focused over the other, so the components API survived for being the better one.

VeeValidate Global API

VeeValidate has been re-written to expose a function-based API, this isn’t a coincidence with the recent events in Vue.js community, it had been planned for a while.

In short, The Validator, ErrorBag classes have been deprecated as they no longer were needed in favor of a new stateless API. So v3 exposes the following functions:

  • extend
  • localize
  • validate
  • configure
  • setInteractionMode
  • ValidationProvider
  • ValidationObserver
  • withValidation

All of which are tree-shakable and more friendly to your bundle size.

validate API

Used to be the verify API, It is stateless, unlike the old Validator class, with this function you can run arbitrary validation for values using the vee-validate rule system and asynchronous validation pipeline with an i18n support without having to integrate components or anything in your project. Heck, you could even use it server-side, or if you are feeling a little rebellious you can use it inside other validation libraries like vuelidate.

Common Uses for this are:

  • Vuex: as you might want to validate values in actions before committing them to the state.
  • Validating values rather than fields, which will come in handy if a certain Vue RFC is implemented 😉.

extend API

Used to be the Validator.extend method.

There are a few problems with the current rule system, especially for Date/Cross-field rules:

  • Date Validation is clunky and is dependent on date-fns implementation which lacks proper timezone support.
  • Fields can only target one field at a time which makes it impossible to cross-field-check multiple fields.
  • Value transformation/normalization before validation is usually needed, but is redundant if implemented as part of the rule.
  • Unnecessary increase to the footprint of vee-validate as date rules are rarely used, compared to required.
  • Impossible to pass objects reliably as rule parameters instead of an array.

To fix this, date rules were deprecated, it is up to you to implement those rules and the new API will make it much easier. You can also use any JS date library you want, date-fns, moment, dayjs to name a few options.

The value transformation/casting API allows you to prepare the value/params before validating. This plays nicely with cross-field validation, which will only transform a field vid to its value, this will be one of the few transformations available out of the box and will not be configurable.

This is also handy when you want your field to emit a value of certain structure but want the custom validator to validate a certain aspect of that structure, for example you want to pass { value: 'xyz', id: 5 } to your model but you only want to validate the value prop.

The proposed API looks like this:

Validator.extend('rule', {
  validate (value, params) {
    // validate method, required if its a new rule, optional if already exists.
  },
  message (fieldName) {
    // message, optional.
  },
  params: [
    { name: 'paramName', isTarget: true }, // applies a locator transform to get the other field value.
    {
      name: 'otherParam',
      cast (value) {
        return new Date(value); // cast the param value before using it.
      }
    },
    // this param uses both transforms to locate the other field, then transforms its value.
    {
      name: 'complex',
      isTarget: true,
      cast (value) {
        return 'whatever'; // Cast the other field value
      }
    },
    immediate: false, // this rule should not trigger when the field is initially validated.
    computesRequired: false, // this rule can change the required state of this field.
  ]
})

Note that the isTarget prop on the rule itself has been deprecated.

This is rather complex but it will be only used in complex rules. So it won’t be used often. Also, it solves the object vs string param formatting, since in the string case it would be necessary to know the order of the params, but in objects, it is necessary to know the names of the params. when you provide a params array, it will always be passed as an object to your rule.

It isn’t required tho, you can still ignore the params array entirely and the params will be passed as-is to your rule, if you provide an object it will be passed as an object, same for the string syntax.

Here is a snippet on how you would implement the after rule:

import { extend } from 'vee-validate';
import { parse, format, isValid, isAfter } from 'date-fns';

extend('after', {
  validate (value, { other }) {
     const parsed = parse(value, 'some-date-format');
     if (!isValid(parsed)) return false;

     return isAfter(value, other);
  },
  // parse the field value.
  castValue: value => parse(targetValue, 'some-date-format', new Date(),
  params: [
    {
      name: 'other',
      isTarget: true,
      cast (targetValue) {
        // parse the target field value.
        return parse(targetValue, 'some-date-format', new Date());
      }
    }
  ]
});

Now its always guaranteed that other will be a date value and since you are parsing the value yourself in the validate function. It is very straightforward to implement complex rules like these.

The extend function options are entirely optional. You can progressively extend and add more options or modify them in your rules during the life-cycle of your application.

import { extend } from 'vee-validate';

// A simpler shorthand for passing params.
extend('ruleFn', {
  params: ['first', 'second'],
  // ... other stuff
});

// New rule without options and passing a validate function directly.
extend('ruleFn', () => {
  return false;
});


// Add/Modify configuration for the rule.
extend('ruleFn', {
  // options..
});

A11y

VeeValidate offered basic accessibility features in v2 and in v3 it will go a step further.

ariaInput

The ValidationProvider slot props expose an ariaInput object which you can bind to your inputs:

<template>
  <ValidationProvider rules="required" v-slot="{ aria }">
    <input type="text" v-model="value" v-bind="aria" />
    <pre>{{ aria }}</pre>
  </ValidationProvider>
</template>

<style>
input.invalid {
  border: solid 1px red;
}

input.valid {
  border: solid 1px green;
}
</style>

ariaMsg

ariaMsg is another set of aria attributes, but you bind it to your error display element. A full example would look like this:

<ValidationProvider rules="required" v-slot="{ errors, ariaInput, ariaMsg }">
  <div>
    <input type="text" v-model="values.classes" v-bind="ariaInput">
    <pre>{{ ariaInput }}</pre>
    <span v-bind="ariaMsg">{{ errors[0] }}</span>
  </div>
</ValidationProvider>

localize API and i18n

The dictionary-based implementation for VeeValidate has been confusing to some, and in the Vue ecosystem, it was not widely used directly. For example, people opted often to use VueI18n since maintaining two validation APIs was problematic, it meant vee-validate had to provide its own adapter for VueI18n which is unnecessary kilobytes in the wire for those who use non-Node.js solutions for their SSR like PHP’s Laravel.

The new API has to be generic, it means the old dictionary had to be hidden away and used behind the scenes. It also means users should be able to have a way to use any i18n library they like.

The localize function is an abstraction for the included simple dictionary, and its signature is identical to the Validator.localize method of the old API.

import { localize } from 'vee-validate';

localize('ar', {
  messages: {
    required: 'هذا الحقل مطلوب'
  }
});

The full dictionary looks like this:

localize({
  en: {
    fields: {
      // custom messages for each field.
    },
    messages: {
      // messages for each rule.
    },
    names: {
      // custom field names.
    }
  }
});

Note that by default vee-validate does not install any locale or rules, you have to import what you need or opt-in to use the full build which has messages and rules loaded beforehand. but with extra footprint cost.

The localize method is only useful if you plan to use the internal i18n dictionary used by vee-validate. But if you want to run your own solution like VueI18n you could do so while installing rules:

import { extend } from 'vee-validate';
import VueI18n from 'vue-i18n';
import { required } from 'vee-validate/dist/rules';

const i18n = new VueI18n({
  locale: 'en',
  messages: {
    en: {
      validation: {
        required: 'This field is required'
      }
    }
  }
});

extend('required', {
  ...required, // rule definition
  message (field, values) {
    return i8n.t('validation.required');
  }
});

Not importing the localize function will drop it from the bundle because its tree-shakable, and will reduce your bundle size down further.

For convenience, vee-validate messages format was updated to be VueI18n compliant, for example:

{
  max: `The {_field_} may not be greater than {length} characters.`,
  max_value: `The {_field_} must be {max} or less.`,
  mimes: `The {_field_} must have a valid file type.`,
  min: `The {_field_} must be at least {length} characters.`,
  min_value: `The {_field_} must be {min} or more.`
}

With that being said, the message generator signature was changed to look like this:

interface ValidationMessageGenerator {
  (field: string, values: { [k: string]: any }): string;
}

Which means params and data are now merged into the same object. A message now can be either a templated message like shown above or a function that returns a string. That means you can use any i18n implementation out there.

Bundle-size

Few rules were removed due to their size and they are very easy to implement:

  • url
  • ip
  • ip_or_fqn
  • credit_card
  • date_format
  • after
  • before
  • date_between
  • decimal (often confused with integer and digits)

This means vee-validate no longer depends on validator.js and will allow you to use whatever version of validator.js to add the rules you need without conflicts from vee-validate internal copy of it.

Some rules behavior has been changed:

The email rule implementation was changed to a simpler regex pattern check, so it might be less accurate than v2 releases.

The length rule will only check if the given string/iterable length matches a specific length that is provided, it will no longer allow max param to be passed.

The rules represent about 4kb on their own, they will be excluded by default and you will need to import the rules your app will be using. There is a full bundle with all the rules pre-configured. This allows us to add more rules in the future without worrying about the bundle size as it has been the case with 2.x releases.

The bundle size of vee-validate v3 has dropped significantly:

Default Bundle 2.x size 3.0-alpha size
Disk 136kb 79kb
Minified 58.7kb 21kb
Minified + Gzipped 16kb 13kb

Full Bundle 2.x size 3.0-alpha size
Disk 350kb 84kb
Minified 124kb 32kb
Minified + Gzipped 31kb 17kb

This means vee-validate is no longer costly and still offers many of the features that made it popular.

About v2

VeeValidate 2.x will still be maintained and checked for bugs, all critical issues will be fixed in a timely manner, but will receive slower updates for newer stuff.

Migration

There will be a migration guide detailing the changes and how to replace each feature with its new implementation.

You can follow the progress here: #2153

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:32
  • Comments:34 (7 by maintainers)

github_iconTop GitHub Comments

18reactions
RomainMazBcommented, Oct 31, 2019

@victor-ponamariov > my workaround to implement it:

extend('url', {
        validate: (str) => {
            var pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol
                '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name
                '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address
                '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path
                '(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string
                '(\\#[-a-z\\d_]*)?$','i'); // fragment locator
            return !!pattern.test(str);
        },
        message: 'This is not a valid URL'
    })

Regex from Zemljoradnik

8reactions
logaretmcommented, Nov 13, 2019

I’m working on it at the moment as part of the new docs, the changes can be tracked here: https://github.com/logaretm/vee-validate/tree/docs/change-structure

I’m trying to build a more interactive experience in the migration guide, as it is a totally different concept and there is no simple steps to follow. Also, vee-validate surface area is large as it handles localization and arbitrary validation as well, so covering the changes in those areas is tricky.

I’m aiming to release 3.1.0 with the new docs by the end of this weekend (Saturday).

Read more comments on GitHub >

github_iconTop Results From Across the Web

VeeValidate 4.0 has been released for Vue 3
Template based validation that is both familiar and easy to setup. Flexible. Validate HTML inputs and Vue components, generate localized errors, Extendable, It ......
Read more >
How to Use VeeValidate 3 for Form Validation
The form validation are rules are registered in main.js so we can use them here. We add :state=”errors.length == 0" in each b-form ......
Read more >
Vue.js form validation with vee-validate v4 | Abdelrahman Awad
... custom components with Vue.js composition API Other talks at this conference 🚀 🪐 https://www.conf42.com​/js2021 — 0 :00 Intro 0 :26 Talk ...
Read more >
vee-validate - npm
Form Validation for Vue.js. Latest version: 4.7.3, last published: a month ago. Start using vee-validate in your project by running `npm i ...
Read more >
VeeValidate 2から3へのアップデート
詳しくはバージョンアップのIssueで作成者が説明されています。 VeeValidate v3.0 🚀 · Issue #2191 · logaretm/vee-validate · GitHub ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found