Update

I wrote a library called vuelidate-messages, which is available on npm. The API and implementation is similar to what is described below.

Intro

Vuelidate does a great job at storing form validation state. It has no support for displaying error messages. One other very popular Vue form validation library, Vee-validate, has great support for making error messages available in your components. It comes at a cost though. Vee-validate will probably add more to your bundle size, and to me the API is a bit more complex than Vuelidate. Vuelidate relies more on conventions in order to reduce the API surface. I also like the symmetry of Vuelidate’s shared API for validating objects and leaf nodes.

If you choose Vuelidate, you then have to tackle the problem of displaying error messages. The most popular packaged solution is vuelidate-error-extractor. I actually haven’t tried it. It might be a good choice, but to me, involving validation message component templates and having to wrap each input indicates that the solution is too complicated. Also, we’re using Vuetify at work. Vuetify already has a prop for passing error messages, and I don’t want to wrap the v-text-field component.

Vuelidate already stores the validation state. Our solution should not store error messages in state, so we can avoid the problem of trying to keep them in sync. That means component data is out of the picture. Computed properties also won’t work, because the shape of the validation object can change, most likely when items are added and removed from arrays. That leaves us with methods. The solution presented here uses Vue component methods to analyze a Vuelidate validation field ($v.name), and return a validation message, if the field is invalid in some way.

Preview

Here is a preview of using the solution in a Vue component.

<template>

<div>

<input v-model="name" />

<span>{{validationMsg($v.name)}}</span>

</div>

</template> <script>

import { required, minLength } from 'vuelidate/lib/validators'

import { validationMessage } from '../our-solution' const validations = {

name: {

required,

txtMinLen: minLength(2)

}

} export default {

data () {

return { name: '' }

},

validations,

methods: {

validationMsg: validationMessage(validations)

}

}

</script>

Step 1 —Validation Message Creators

The first part of the solution is to provide the messages. Here we’re using functions as a simple way to support using params in the message. The function will also be called with the context of the Vue component, so the function could access the VueI18n API. Be aware though that since arrow functions are bound to the context in which they’re created, they don’t have access to the Vue component instance.

const plural = (words, count) => {

words = words.split('|').map(x => x.trim())

return count > 1 ? words[2].replace('{n}', count) : words[count]

} export const validationMessageCreators = {

required: () => 'Required',

email: () => 'Invalid email',

txtMinLen: ({ $params }) => {

const min = plural(

'no characters | one character | {n} characters',

$params.txtMinLen.min

)

return `Must be at least ${min}`

}

}

This solution depends on matching keys in your Vuelidate validations object with the keys in your validationMessageCreators object. For example, to use the txtMinLen validation message, your validations object would have this:

const validations = {

name: {

txtMinLen: minLength(2)

}

}

Step 2 — Parse validations object for validator keys

Update: I didn’t include step 2 in the npm package.

This function will parse your validations object recursively and build a list of all the keys of the validators. The main reason for doing this is that it allows us to check (in step 3) that we have messages for all validators.

const isFn = x => typeof x === 'function' // Given a Vuelidate validations object, find the validator keys

function extractValidatorKeys (validations) {

const keys = Object.keys(validations)

const validatorKeys = keys.filter(x => isFn(validations[x]))

const nested = keys.filter(x => !isFn(validations[x]))

return validatorKeys.concat(

...nested.map(x => extractValidatorKeys(validations[x]))

)

Step 3 — Vue component method

Here’s the function that we export. You’ll curry it with the validation messages and the validations object, and you get back a function that accepts a Vuelidate field. I chose to only return one error. I don’t like displaying more than one error at a time per input field, but you could modify it easily to return an array.

export const getValidationMessage = messages => validations => {

let keys = extractValidatorKeys(validations) // check to make sure all validators have error messages

const missing = keys.filter(x => !(x in messages))

if (missing.length) {

console.warn(

'Validators missing validation messages: %s',

missing.join(', ')

)

// remove keys that don't have validation messages

keys = keys.filter(x => missing.indexOf(x) < 0)

}



// Vue component method

// Given a vuelidate field object, maybe return a string

return function (...args) {

let field = args[0]

if (!field.$error) return ''

for (let i = 0; i < keys.length; i++) {

let key = keys[i]

if (field[key] === false) {

return messages[key].apply(this, args)

}

}

return ''

}

}

Step 4— Export curried util

Lastly we’re going to curry the utility with the validationMessageCreators to make importing easier.

export const validationMessage =

getValidationMessage(validationMessageCreators)

Conclusion

The solution is essentially mapping validation keys to error messages. I’m not sure, but I assume the Vue component method, referenced as “errors” in the example, runs for each input each time the component updates. There’s a minimal amount of code in that function, and I haven’t noticed any sluggish behavior, but if you have a very large form, you may need an optimization technique, like memoization, using computed properties, or breaking the form into multiple components.

vuelidate-messages package