How I Solved Vue Form Validation Headaches

Chris Washington
5 min readJan 22, 2020

--

VRxForm: A Reactive Vue Form Validations Library

The problem

One of the more time consuming aspects of building modern web applications is handling form validations. Most developers I have worked with (including myself) find that the amount of validations they have to add to code and the extra unit tests as a result, often makes a simple aspect of web development, tedious and dreadful (and can introduce unnecessary bugs).

Although there have been a couple of libraries that have tried to ease this pain, I didn’t find them as either powerful nor painless to use, so I set out to create VRx Form.

The Solution: What is Vrx Form?

VRx Form is a configuration first code based solution to form validations. This means that all validations are configured, and code is used only to get the validation results (much unlike other libraries present which rely heavily on dom based configuration).

VRx Form seeks to do the following:

  • Ease the pain of validations by making them configurable.
  • Automate error message population making it easier to display and figure out what error messages are present.
  • Lesson the burden of maintaining complex forms.
  • Ensures the solution works with custom form field web-components by extending the current v-model spec and making it configurable.

Installation

> npm i vrx-form

Note: This is currently in beta, however the api is nailed down. There are a few more enhancements both to package dependency size (currently adds 74kb to a Vue build) and features around state that need to be added.

Setup

Once installed, in the main.js file of the Vue project add the following lines of code to install the plugin:

import Vue from "vue";
import VRXForm from "vrx-form";
Vue.use(VRXForm);

Creating the form

Once the plugin is installed, we can initialize a form in a component:

<template>
<form v-form="myForm">
<div>
<label for="myInput">My Input</label>
</div>
<div>
<input v-model="myData.myInput" v-form-field>
</div>
</form>
</template>
<script>
export default {
data() {
return {
myForm: this.$createForm(),
myData: {
myInput: null
}
};
}
}
</script>

Let’s break this down step by step:

First, notice the directive v-form on the form element. It has a reference to myForm which is initialized in data with this.$createForm() which creates a VRXForm object. This object can do many things as we will see later in this article.

Next, notice the directive on the input element, v-form-field . This must be used alongside the v-model directive (v-form-field extends the functionality of v-model).

Creating the validations

Just initializing the form isn’t enough, we also need validations. We will create a few easy ones here, but you can go further in-depth in the documentation.

Once the component is mounted we can add the validations. In the mounted method add the following:

// in the beginning of the script section
import { VRXFormValidatorTypes } from "vrx-form";
mounted() {
this.myForm.
setValidations({
myInput: [
{
type: VRXFormValidatorTypes.REQUIRED,
validation: false
},
{
type: VRXFormValidatorTypes.RANGE_LENGTH
validation: [8,16],
message: "Must be between 8 and 16 characters long"
}
]
})
.init();
}

The above code adds two validators to the myInput field. First it makes the field not required. Second, it makes sure that if and only if data is entered, that it must be between 8 and 16 characters long. Notice that it also provides an optional error message if the validation returns false.

Note: the keys passed to setValidations should be the same as your data object. In our example myData has a key named myInput . Therefore to validate myInput we need to pass that same key into setValidations with its validators like above.

Also take note that init is called. Validations don’t start to happen until init is called, this is in case an api needs to be called and data retrieved before validations happen.

Showing errors

Now that validations happen on user input, we want to show errors. We need to create a computed property as well as add a div to the template to show errors once the input field is blurred:

Updated template:

<template>
<form v-form="myForm">
<div>
<label for="myInput">My Input</label>
</div>
<div>
<input v-model="myData.myInput" v-form-field>
</div>
<div v-if="myInputError">{{myInputError}}</div>
</form>
</template>

Computed method:

computed: {
myInputError() {
return this.myForm.getError("myInput");
}
}

Here we have added a div to show the computed property myInputError whenever it is populated.

There is a lot more to the error state and other state information available when it comes to errors. Make sure to read the documentation for more information on how retrieving errors works.

Note: Some times it is necessary to show errors as user input happens, by default the library has lazy error population meaning that the errors only show when the input field is no longer in focus. Check out the active-error section to see how to show errors in real time: Active Errors.

Handling submit

The last step is knowing when the form is valid. We need to know two things when checking for form validity, is the form dirty (meaning values changed) and if the form is valid (meaning all validations have passed). We can create another computed method to do this:

computed: {
isValid() {
return this.myForm.isDirty && this.myForm.isValid;
}
}

I like to check if a form isDirty so that I know whether or not the information has changed at all.

From there we can add a submit method:

methods: {
onSubmit() {
//just print out the result
console.log(this.myData);

// and then clear the form if you need to
this.myForm.$clearForm();
}
}

And put those to use in the updated template:

<template>
<form v-form="myForm">
<div>
<label for="myInput">My Input</label>
</div>
<div>
<input v-model="myData.myInput" v-form-field>
</div>
<div v-if="myInputError">{{myInputError}}</div>
<div>
<button @click="onSubmit" :disabled="!isValid">submit</button>
</div>
</form>
</template>

The button that was added will call onSubmit when clicked, if and only if isValid is true.

Putting it all together

Here is what the final code looks like:

<template>
<form v-form="myForm">
<div>
<label for="myInput">My Input</label>
</div>
<div>
<input v-model="myData.myInput" v-form-field>
</div>
<div v-if="myInputError">{{myInputError}}</div>
<div>
<button @click="onSubmit" :disabled="!isValid">submit</button>
</div>
</form>
</template>
<script>
import { VRXFormValidatorTypes } from "vrx-form";
export default {
mounted() {
this.myForm.
setValidations({
myInput: [
{
type: VRXFormValidatorTypes.REQUIRED,
validation: false
},
{
type: VRXFormValidatorTypes.RANGE_LENGTH
validation: [8,16],
message: "Must be between 8 and 16 characters long"
}
]
})
.init();
},
data() {
return {
myForm: this.$createForm(),
myData: {
myInput: null
}
};
},
methods: {
onSubmit() {
//just print out the result
console.log(this.myData);

// and then clear the form if you need to
this.myForm.$clearForm();
}
},
computed: {
isValid() {
return this.myForm.isDirty && this.myForm.isValid;
},
myInputError() {
return this.myForm.getError("myInput");
}
}
}
</script>

This is a pretty clean and powerful way to do validations.

Conclusion

There is so much more to this library and I hope it brings an ease in pain to form validations as it has done for me.

Check out a full example of validations on a form to see the power of this library: Full Example.

--

--

Chris Washington
Chris Washington

Written by Chris Washington

Chris is a Sr. Lead Software Engineer at Capital One just doing what he loves: creating epic things, and writing about them.

No responses yet