Login to bookmark this video
Buy Access to Course
43.

Client-Side Validation

Share this awesome video!

|

Keep on Learning!

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Click to go back to view the shopping cart... which is now empty. We're not done shopping! Add another inflatable sofa. Awesome!

Server-side validation is a must: we can't have API endpoints that allow any data. And we've handled that nicely. Go us!

Client-side validation in JavaScript is optional. It requires extra work, but it can make your form even nicer to use. So let's add some! We'll keep it simple: on "blur" of a field - so when we leave a field - if that field is empty, we'll immediately show a validation error.

Start inside of our checkout form component... down in methods. Add a new one called validateField(). I'll paste in the start: an object that contains the validation messages that should be used for each field if that field is blank.

161 lines | assets/components/checkout/index.vue
// ... lines 1 - 71
<script>
// ... lines 73 - 77
export default {
name: 'CheckoutForm',
// ... lines 80 - 104
methods: {
// ... lines 106 - 147
validateField() {
const validationMessages = {
customerName: 'Please, enter your full name!',
customerEmail: 'Please, enter your email address!',
customerAddress: 'Please, enter your street address!',
customerZip: 'Please, enter your ZIP code!',
customerCity: 'Please, enter your city!',
customerPhone: 'Please, provide a phone number!',
};
},
},
};
</script>

Listening to blur on a Custom Component

Before we add the real logic to this method, let's call this "on blur" of each field. Easy! Head up to the template and add @blur="validateField".

175 lines | assets/components/checkout/index.vue
<template>
<div class="row p-3">
<div class="col-12">
<form @submit.prevent="onSubmit">
// ... lines 5 - 12
<div class="form-row">
<form-input
// ... lines 15 - 17
@blur="validateField"
/>
// ... lines 20 - 27
</div>
// ... lines 29 - 72
</form>
</div>
</div>
</template>
// ... lines 77 - 175

Except... bah! That won't work! This is a custom component, not a normal form element.

Okay, don't panic. Go into form-input.vue. And on the actual <input> element, add @blur=""... and then we'll just emit that same blur event from here... and pass it the same event data.

62 lines | assets/components/checkout/form-input.vue
<template>
<div class="form-group">
// ... lines 3 - 8
<input
// ... lines 10 - 18
@blur="$emit('blur', $event)"
>
// ... lines 21 - 26
</div>
</template>
// ... lines 29 - 62

We're basically propagating that event up so that our parent component can listen to it.

Back in index.vue, now that this should work, copy the @blur and repeat this on all six fields... super fast!

175 lines | assets/components/checkout/index.vue
<template>
<div class="row p-3">
<div class="col-12">
<form @submit.prevent="onSubmit">
// ... lines 5 - 12
<div class="form-row">
<form-input
v-model="form.customerName"
// ... lines 16 - 17
@blur="validateField"
/>
<form-input
v-model="form.customerEmail"
// ... lines 23 - 25
@blur="validateField"
/>
</div>
<form-input
v-model="form.customerAddress"
// ... line 32
@blur="validateField"
/>
<div class="form-row">
<form-input
v-model="form.customerZip"
// ... lines 39 - 40
@blur="validateField"
/>
<form-input
v-model="form.customerCity"
// ... lines 46 - 47
@blur="validateField"
/>
<form-input
v-model="form.customerPhone"
// ... lines 53 - 55
@blur="validateField"
/>
</div>
// ... lines 59 - 72
</form>
</div>
</div>
</template>
// ... lines 77 - 175

Filling in the Validation Logic

Ok! Head back down to validateField()... here it is. The plan is: read the id attribute of whatever input was just blurred, use that to see if the form data for that field is empty and, if it is, add a new item to the validationErrors data.

To read the id attribute, we need the event argument:

175 lines | assets/components/checkout/index.vue
// ... lines 1 - 77
<script>
// ... lines 79 - 83
export default {
name: 'CheckoutForm',
// ... lines 86 - 110
methods: {
// ... lines 112 - 153
validateField(event) {
// ... lines 155 - 170
},
},
};
</script>

Then, down here, we can say const validationField = event.target.id:

175 lines | assets/components/checkout/index.vue
// ... lines 1 - 77
<script>
// ... lines 79 - 83
export default {
name: 'CheckoutForm',
// ... lines 86 - 110
methods: {
// ... lines 112 - 153
validateField(event) {
// ... lines 155 - 163
const validationField = event.target.id;
// ... lines 165 - 170
},
},
};
</script>

so that will be something like customerName or customerEmail. Then, if not this.form[validationField], then we know that field is empty. Add a validation error with this.validationErrors[validationField] = validationMessages[validationField].

175 lines | assets/components/checkout/index.vue
// ... lines 1 - 77
<script>
// ... lines 79 - 83
export default {
name: 'CheckoutForm',
// ... lines 86 - 110
methods: {
// ... lines 112 - 153
validateField(event) {
// ... lines 155 - 163
const validationField = event.target.id;
if (!this.form[validationField]) {
this.validationErrors[validationField] = validationMessages[validationField];
// ... lines 168 - 169
}
},
},
};
</script>

Oh, and don't forget an else: in case the user just went back... filled something in... and then blurred. If there was already an error before, now we need to remove it. Do that with delete this.validationErrors[validationField].

175 lines | assets/components/checkout/index.vue
// ... lines 1 - 77
<script>
// ... lines 79 - 83
export default {
name: 'CheckoutForm',
// ... lines 86 - 110
methods: {
// ... lines 112 - 153
validateField(event) {
// ... lines 155 - 163
const validationField = event.target.id;
if (!this.form[validationField]) {
this.validationErrors[validationField] = validationMessages[validationField];
} else {
delete this.validationErrors[validationField];
}
},
},
};
</script>

The reason I'm using delete and not equals null is... well... that's just the way that our validationErrors object works right now. It starts as an empty object and we only put things onto it when they have an error. We even reset it back to an empty object on submit. This will actually cause a "reactivity" problem... which we'll explore in a few minutes.

By the way, there are some Vue libraries to help with validation, like Vuelidate. You can totally check these out. They make adding a lot of complex validation rules a lot easier and cleaner. Right now, we're just checking to see if the field is blank. But even with something like Vuelidate, you're still basically doing the same thing: you're storing the errors for a field somewhere like validationErrors and then using that when you render to display the error.

Anyways, let's test this thing! Find your browser, click "Check Out", click into the name field... and hit tab. Huh. Nothing happened. Go check out the Vue dev tools, find CheckoutForm... then down to validationErrors. Yea, it says that we do have two validation errors for name and email: the two fields we blurred.

So the data is right.... but they're not rendering on the form. Want to see something even weirder? Go into one of the other boxes and type something. Boom! As soon as we do, both error messages show up! We don't see any errors until we modify a field.

What the heck is going on? The short answer is that we accidentally removed reactivity from the validationErrors data. That's a fancy way of saying that when we change a value on validationErrors, Vue does not realize that it needs to re-render. So the data is being stored correctly... but it doesn't reflect on the page until Vue re-renders for some other reason, like us changing some of the form data.

Next: let's explore this deeper... and fix it!