Form validation is a feature that’s not built into Vue.js.
However, we still need this feature very much.
In this article, we’ll look at how to persist form validation states for unmounted components.
Also, we look at how to nest ValidationObservers
.
Persisting Provider State
We can persist validation state with the keep-alive
component.
With it, even if the component isn’t displayed, the components inside it won’t be unmounted.
For example, we can write:
main.js
import Vue from "vue";
import App from "./App.vue";
import { ValidationProvider, ValidationObserver, extend } from "vee-validate";
import { required, alpha, email } from "vee-validate/dist/rules";
extend("required", required);
extend("alpha", alpha);
extend("email", email);
Vue.component("ValidationProvider", ValidationProvider);
Vue.component("ValidationObserver", ValidationObserver);
Vue.config.productionTip = false;
new Vue({
render: h => h(App)
}).$mount("#app");
App.vue
:
<template>
<div id="app">
<ValidationObserver ref="form">
<form @submit.prevent="onSubmit">
<keep-alive>
<ValidationProvider
v-if="currentStep === 1"
name="email"
rules="required|email"
v-slot="{ errors }"
>
<input v-model="email" type="text" placeholder="email">
<span>{{ errors[0] }}</span>
</ValidationProvider>
</keep-alive>
<keep-alive>
<ValidationProvider v-if="currentStep === 2" rules="required|alpha" v-slot="{ errors }">
<input v-model="name" name="name" type="text" placeholder="name">
<span>{{ errors[0] }}</span>
</ValidationProvider>
</keep-alive>
<button type="button" @click="goToStep(currentStep - 1)">Previous</button>
<button
type="button"
[@click](http://twitter.com/click "Twitter profile for @click")="goToStep(currentStep + 1)"
>{{ currentStep === 2 ? 'Submit' : 'Next' }}</button>
</form>
</ValidationObserver>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
currentStep: 1,
name: "",
email: ""
};
},
methods: {
goToStep(step) {
if (step < 1) {
return;
}
if (step > 2) {
this.onSubmit();
return;
}
this.currentStep = step;
},
async onSubmit() {
const success = await this.$refs.form.validate();
if (!success) {
return;
}
alert("submitted");
this.name = "";
this.$nextTick(() => {
this.$refs.form.reset();
});
}
}
};
</script>
We have a multi-step form.
Each field is wrapped inside a keep-alive
component.
ValidationProvider
is inside keep-alive
.
This way, we can keep the validation state of the field even if it’s hidden by v-if
.
We have 2 buttons to go to the next step and go back to the previous step with the goToStep
method.
The onSubmit
method checks the form with the ValidationObserver
‘s ref, which returns a promise that resolves to true
is all fields are valid and false
otherwise.
We proceed with form submission if success
is true
.
Nested Observers
We can nest ValidationObservers
.
All the fields are checked by the parent ValidationObserver
if they’re nested.
For example, we can write:
<template>
<div id="app">
<ValidationObserver ref="form">
<form @submit.prevent="onSubmit">
<ValidationObserver>
<ValidationProvider name="email" rules="required|email" v-slot="{ errors }">
<input v-model="email" type="text" placeholder="email">
<span>{{ errors[0] }}</span>
</ValidationProvider>
</ValidationObserver>
<ValidationObserver>
<ValidationProvider rules="required|alpha" v-slot="{ errors }">
<input v-model="name" name="name" type="text" placeholder="name">
<span>{{ errors[0] }}</span>
</ValidationProvider>
</ValidationObserver>
<br>
<input type="submit" value="submit">
</form>
</ValidationObserver>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
name: "",
email: ""
};
},
methods: {
async onSubmit() {
const success = await this.$refs.form.validate();
if (!success) {
return;
}
alert("submitted");
this.name = "";
this.email = "";
this.$nextTick(() => {
this.$refs.form.reset();
});
}
}
};
</script>
We have ValidationObserver
component inside another one.
Both fields are still validated as a whole by the root ValidationObserver
.
We can also write:
<template>
<div id="app">
<ValidationObserver ref="form" v-slot="{ handleSubmit }">
<form @submit.prevent="handleSubmit(onSubmit)">
<ValidationObserver>
<ValidationProvider name="email" rules="required|email" v-slot="{ errors }">
<input v-model="email" type="text" placeholder="email">
<span>{{ errors[0] }}</span>
</ValidationProvider>
</ValidationObserver>
<ValidationObserver>
<ValidationProvider rules="required|alpha" v-slot="{ errors }">
<input v-model="name" name="name" type="text" placeholder="name">
<span>{{ errors[0] }}</span>
</ValidationProvider>
</ValidationObserver>
<br>
<input type="submit" value="submit">
</form>
</ValidationObserver>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
name: "",
email: ""
};
},
methods: {
onSubmit() {
alert("submitted");
this.name = "";
this.email = "";
this.$nextTick(() => {
this.$refs.form.reset();
});
}
}
};
</script>
We use the built-in handleSubmit
function to check for form validation state before we run our own onSubmit
method.
Therefore, we don’t need to call validate
on the root ValidationObserver
‘s ref any more.
Conclusion
We can persist validation state if a component is unmounted with the keep-alive
component.
Also, we can nested ValidationObserver
components.