Categories
Vue

Vue Tips — Slots, Vue Router, and Mutations

Vue.js is a popular framework for creating front end web apps.

In this article, we’ll look at some tips for writing better Vue.js apps.

How to Pass Down Slots Inside Wrapper Component

To pass down slots in a wrapper component, we can loop through all the slots and pass them down to the child.

For instance, we can write:

<wrapper>
  <parent-table v-bind="$attrs" v-on="$listeners">

    <slot v-for="slot in Object.keys($slots)" :name="slot" :slot="slot"/>

    <template v-for="slot in Object.keys($scopedSlots)" :slot="slot" slot-scope="scope">
      <slot :name="slot" v-bind="scope"/>
    </template>

  </parent-table>
</wrapper>

We get the slots with the $slots variable.

Then we use Object.keys to get names of the slots so that we can loop through all of them and pass the name down.

Likewise, we can loop through the scoped slots with the $scopedSlots variables.

We get the keys the same way and loop through them with v-for the same way.

With Vue 2.6, the v-slot= directive is introduced to let is pass the slots down.

For instance, we can write:

<wrapper>
  <parent-table v-bind="$attrs" v-on="$listeners">
    <template v-for="(_, slot) of $scopedSlots" v-slot:[slot]="scope">
      <slot :name="slot" v-bind="scope"/>
    </template>
  </parent-table>
</wrapper>

We loop through the slot with v-for .

We get the scoped slots with the $scopedSlots variable.

slot is the slot name again.

This time, we pass it to the v-slot directive as a modifier to pass down the named slot.

scope has the scope from the scoped slot.

We use v-bind to get the scope.

And we

Alternatively, we can use render function to pass down the slots.

For instance, we can write:

render(h) {
  const children = Object.keys(this.$slots)
    .map(slot => h('template', { slot }, this.$slots[slot]))

  return h('wrapper', [
    h('parent-table', {
      attrs: this.$attrs,
      on: this.$listeners,
      scopedSlots: this.$scopedSlots,
    }, children)
  ])
}

We get the slots with the this.$slots property.

We call Object.keys to get the slot names.

And we call map on it to map the slot names to the template components.

And we pass in the slots name and the scope down,.

Then we return a wrapper component with the parent-table component with the listeners, attributes, and scoped slots and children as the children.

Get Query Parameters from a URL in Vue.js

We can get the query parameters from a URL in Vue with the this.$route.query property.

To get the query parameter foo=bar , we write:

this.$route.query.foo

and we get 'bar' as the value.

This is available assuming we’re using Vue Router in our Vue app.

If we haven’t added it, we can write:

index.html

<script src="https://unpkg.com/vue-router"></script>

index.js

const router = new VueRouter({
  mode: 'history',
  routes: [
    {
      path: '/page',
      name: 'page',
      component: PageComponent
    }
  ]
});

const vm = new Vue({
  router,
  el: '#app',
  mounted() {
    const q = this.$route.query.q;
    console.log(q)
  },
});

to get it.

We create the VueRouter instance and pass it into the object we passed into the Vue constructor.

routes has the routes.

Passing Multiple Parameters to a Mutation with Vuex

To pass multiple parameters to action with Vuex, we can pass in an object as the payload.

For instance, we can create our mutation by writing:

mutations: {
  setToken(state, { token, expiration }) {
    localStorage.setItem('token', token);
    localStorage.setItem('expiration', expiration);
  }
}

We have an object as the second parameter.

It has the token and expiration properties.

Then we can invoke the mutation by writing:

store.commit('setToken', {
  token,
  expiration,
});

We invoke the setToken mutation with the token and expiration properties in an object as the 2nd argument.

Reload Route with Vue Router

To reload a route with Vue Route, we can call the this.$router.go() method.

If it has no arguments, then it’ll reload the current route.

We can also add an unique value for the key prop to the router view:

<router-view :key="$route.fullPath"></router-view>

This way, it’ll notice when the path changes and it’ll trigger a reload of the component with new data.

Conclusion

We can reload our route with this.$router.go() .

There are many ways to pass a scope down to a child.

We can get query parameters in our component if we use Vue Router.

To pass in multiple pieces of data into a mutation, we can pass in an object with all the data we want into the mutation.

Categories
Vue

Vee-Validate — Required Fields and Built-in Rules

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 define required fields and use built-in validation rules.

Required Fields

A field is required if we must have it present.

Values that are empty or falsy can’t be accepted.

Therefore, undefined , null , empty strings and empty arrays can’t be accepted.

0 is accepted as a valid value for the required fields.

false is also accepted by Vee-Validate as a valid value.

Optional Fields

Optional fields are validated when they’re empty.

Create a Required Rule

We can create a required rule with the computesRequired property.

We set it to true for it to be required.

For instance, we can write:

extend('required', {
  validate () {
    // ...
  },
  computesRequired: true
});

In the validate method, we have to return an object with the required and valid properties.

For example, we can write:

extend('required', {
  validate (value) {
    return {
      required: true,
      valid: !['', null, undefined].includes(value)
    };
  },
  computesRequired: true
});

Built-in Rules

Vee-Validate comes with come validation rules.

However, they aren’t installed by default.

To use them, we have to import them:

import { extend } from 'vee-validate';
import { required, email } from 'vee-validate/dist/rules';

extend('email', email);
extend('required', {
  ...required,
  message: 'please fill in'
});

We have the message property which overrides the existing one.

Install All Rules

We can install all rules by importing everything from the vee-validate/dist/rules module.

For example, we can write:

import { extend } from 'vee-validate';
import * as rules from 'vee-validate/dist/rules';

Object.keys(rules).forEach(rule => {
  extend(rule, rules[rule]);
});

The rules are in the rules object properties, so we just loop through them and call extend on them to import them.

Rules

There are many rules available. The following can be imported and use:

  • alpha — alphabetic characters
  • alpha_dash — alphabetic characters with dashes or underscores
  • alpha_num — alphanumeric characters
  • alpha_spaces — alphabets and spaces
  • between — minimum and maximum values
  • confirmed — check that 2 fields are the same
  • digits — a specified number of digits
  • dimensions — dimensions of images
  • email — emails
  • excluded — check if something isn’t in the list
  • ext — checks file extensions
  • image — image mime type
  • integer — integers
  • is — match the given value
  • is_not — doesn’t match the given value
  • length — length of the value
  • max — check if something doesn’t exceed the maximum length
  • max_value — check if something doesn’t exceed the maximum value
  • mimes — check if something has one of the listed mime types
  • min — less than the specified length
  • min_value — check if something isn’t less than the given value
  • numeric — numeric characters
  • oneOf — check if the inputted value is one of the listed ones
  • regex — check if a value matches a given regex
  • required — check if something is entered
  • required_if — required if something is entered in another field
  • size — check if a file doesn’t exceed a given size in kilobytes

Some of them take arguments.

digits can be used as follows:

<ValidationProvider rules="digits:10" v-slot="{ errors }">
  <input v-model="value" type="text">
  <span>{{ errors[0] }}</span>
</ValidationProvider>

where 10 is the number of digits allowed

dimensions checks the dimensions of the image file selected:

<ValidationProvider rules="dimensions:220,230" v-slot="{ errors, validate }">
  <input type="file" @change="validate">
  <span>{{ errors[0] }}</span>
</ValidationProvider>

220 is the width and 230 is the height.

excluded checks if something isn’t in the list:

<ValidationProvider rules="excluded:1,2" name="number" v-slot="{ errors }">
  <select v-model="value">
    <option value="1">one</option>
    <option value="2">two</option>
    <option value="3">three</option>
  </select>
  <span>{{ errors[0] }}</span>
</ValidationProvider>

1 and 2 are arguments of excluded , so they’re invalid.

ext checks if a selected file has the given extension specified:

<ValidationProvider rules="ext:jpg,png" v-slot="{ errors, validate }">
  <input type="file" @change="validate">
  <span>{{ errors[0] }}</span>
</ValidationProvider>

So jpg and png are valid file extensions.

is_not takes an argument to exclude.

For example, we can write:

<ValidationProvider rules="is_not:foo" v-slot="{ errors }">
  <input type="text" v-model="value">
  <span>{{ errors[0] }}</span>
</ValidationProvider>

foo is the value that’s not valid when entered.

length means that the string must be a string or array value that must have the given length.

Therefore, if we have:

<ValidationProvider rules="length:5" v-slot="{ errors }">
  <input type="text" v-model="value">
  <span>{{ errors[0] }}</span>
</ValidationProvider>

Then the inputted value must have length 5.

Conclusion

There are many built-in rules we can use for validation.

Inputted values including files can be validated.

We can also define the required fields.

Categories
Vue

Vee-Validate — Localization, and Input Component

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 add localization features with Vee-Validate. We also look at how to extract the input component code.

Lazily Import Locales

We can use the import function to import a locale file from Vee-Validate.

For example, we can write:

import { localize } from 'vee-validate';

import(`vee-validate/dist/locale/fr.json`).then(locale => {
  localize(code, locale);
});

Using Other i18n Libraries

Vee-Validate integrates with other i18n libraries. For instance, we can use the vue-i18n library with Vee-Validate.

We can write:

import Vue from "vue";
import App from "./App.vue";
import { ValidationProvider, extend } from "vee-validate";
import { required } from "vee-validate/dist/rules";
import en from "vee-validate/dist/locale/en.json";
import VueI18n from "vue-i18n";

Vue.use(VueI18n);

const i18n = new VueI18n({
  locale: "en",
  messages: {
    en: {
      validation: en.messages
    }
  }
});

extend("required", {
  ...required,
  message: (_, values) => i18n.t("validation.required", values)
});

Vue.component("ValidationProvider", ValidationProvider);

Vue.config.productionTip = false;

new Vue({
  i18n,
  render: h => h(App)
}).$mount("#app");

We imported the messages from Vee-Validate.

Then we set them as the property of messages.en.validation .

Then we use i18n.t to translate the message.

Then we can write:

<template>
  <div id="app">
    <ValidationProvider name="name" rules="required" v-slot="{ errors }">
      <input v-model="name" type="text" placeholder="name">
      <span>{{ errors[0] }}</span>
    </ValidationProvider>

<input type="submit" value="submit">
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      name: ""
    };
  }
};
</script>

to display the message.

Model-less Validation

We can validate inputted values without models.

For example, we can write:

main.js

import Vue from "vue";
import App from "./App.vue";
import { ValidationProvider, extend } from "vee-validate";
import { required, image } from "vee-validate/dist/rules";

extend("required", required);
extend("image", image);
Vue.component("ValidationProvider", ValidationProvider);

Vue.config.productionTip = false;

new Vue({
  render: h => h(App)
}).$mount("#app");

App.vue

<template>
  <div id="app">
    <ValidationProvider rules="required|image" name="file" v-slot="{ validate, errors }">
      <input type="file" @change="validate">
      <p>{{ errors[0] }}</p>
    </ValidationProvider>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

We imported the required and image rules in main.js .

Then we used ValidationProvider in the template.

We called the built-in validate function to validate the file type when it’s changed.

We also have the name prop on the ValidationProvider .

Then we’ll see an error message if it’s not a valid image file.

Custom Component File Validation

Any component that emits an input event can be validated by Vee-Validate.

The value is emitted with the input event.

Value Synchronization

We can synchronize the value manually between what’s entered and what Vee-Validate sees with the syncValue method.

For example, we can write:

this.$refs.provider.syncValue(newValue);

to set the value of the validation provider with newValue .

Dynamic Rules

Rules can be applied dynamically.

We can put an expression in the rules prop.

For example, we can write:

<template>
  <div id="app">
    <button @click="required = !required">{{required ? 'disable':'enable'}} required</button>
    <br>
    <ValidationProvider
      :rules="`${required ? 'required' : ''}`"
      name="value"
      v-slot="{ validate, errors }"
    >
      <input v-model="value" type="text">
      <p>{{ errors[0] }}</p>
    </ValidationProvider>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      value: "",
      required: true
    };
  }
};
</script>

Then when required is false , no validation will be done.

Otherwise, validation will be done.

Extracting Input Fields

We can extract input fields into their own component.

This way, we don’t have to repeatedly reference ValidationProvider .

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, image } from "vee-validate/dist/rules";

extend("required", required);
extend("image", image);
Vue.component("ValidationProvider", ValidationProvider);
Vue.component("ValidationObserver", ValidationObserver);

Vue.config.productionTip = false;

new Vue({
  render: h => h(App)
}).$mount("#app");

TextInput.vue

<template>
  <ValidationProvider tag="div" :rules="rules" :name="name" :vid="vid" v-slot="{ errors }">
    <input :type="type" v-model="currentValue">
    <span>{{ errors[0] }}</span>
  </ValidationProvider>
</template>

<script>
import { ValidationProvider } from "vee-validate";

export default {
  name: "TextInput",
  components: {
    ValidationProvider
  },
  props: {
    value: {
      type: String,
      default: ""
    },
    rules: {
      type: [String, Object],
      default: ""
    },
    name: {
      type: String,
      default: ""
    },
    vid: {
      type: String,
      default: undefined
    },
    type: {
      type: String,
      default: "text"
    }
  },
  data: () => ({
    currentValue: ""
  }),
  watch: {
    currentValue(val) {
      this.$emit("input", val);
    }
  }
};
</script>

The TextInput component is the component with the form field logic.

It takes various props that are used to populate fields with the data we want.

The value entered with the input event in the currentValue watcher.

It also takes the value prop.

Therefore, it can bind to v-model .

App.vue

<template>
  <div id="app">
    <ValidationObserver v-slot="{ handleSubmit }">
      <form @submit.prevent="handleSubmit(onSubmit)">
        <TextInput v-model="firstName" name="First Name" rules="required"/>

        <TextInput v-model="lastName" name="Last Name" rules="required"/>

        <input type="submit" value="submit">
      </form>
    </ValidationObserver>
  </div>
</template>

<script>
import TextInput from "./components/TextInput";

export default {
  name: "App",
  components: {
    TextInput
  },
  data() {
    return {
      firstName: "",
      lastName: ""
    };
  },
  methods: {
    onSubmit() {
      alert("success");
    }
  }
};
</script>

Then we can use it in the form element like any other input element.

Conclusion

We can add localization and extract form fields into a component.

Categories
Vue

Vee-Validate — Localization

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 add localization features with Vee-Validate.

Localization

We can add localization features with Vee-Validate.

This way, we can display different messages for validation in different languages.

For instance, we can import the localize function by writing:

main.js

import Vue from "vue";
import App from "./App.vue";
import { localize, ValidationProvider, extend } from "vee-validate";
import { required } from "vee-validate/dist/rules";

localize({
  en: {
    messages: {
      required: "this field is required"
    }
  }
});
extend("required", required);

Vue.component("ValidationProvider", ValidationProvider);

Vue.config.productionTip = false;

new Vue({
  render: h => h(App)
}).$mount("#app");

Then we can display the message by writing:

<template>
  <div id="app">
    <ValidationProvider name="email" rules="required" v-slot="{ errors }">
      <input v-model="email" type="text" placeholder="email">
      <span>{{ errors[0] }}</span>
    </ValidationProvider>

<input type="submit" value="submit">
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      email: ""
    };
  }
};
</script>

We’ll see the ‘this field is required’ message is we entered nothing into the field.

Installing Locales

Vee-Validate comes with locale data built-in.

For example, we can write:

import Vue from "vue";
import App from "./App.vue";
import { localize, ValidationProvider, extend } from "vee-validate";
import { required } from "vee-validate/dist/rules";
import en from "vee-validate/dist/locale/en.json";
import fr from "vee-validate/dist/locale/fr.json";

localize({
  en,
  fr
});
localize("fr");
extend("required", required);

Vue.component("ValidationProvider", ValidationProvider);

Vue.config.productionTip = false;

new Vue({
  render: h => h(App)
}).$mount("#app");

Then we imported the English and French locales.

We can set the locale by using the localize method.

For example, we can write:

main.js

import Vue from "vue";
import App from "./App.vue";
import { localize, ValidationProvider, extend } from "vee-validate";
import { required } from "vee-validate/dist/rules";
import en from "vee-validate/dist/locale/en.json";
import fr from "vee-validate/dist/locale/fr.json";

localize({
  en,
  fr
});
localize("fr");
extend("required", required);

Vue.component("ValidationProvider", ValidationProvider);

Vue.config.productionTip = false;

new Vue({
  render: h => h(App)
}).$mount("#app");

Then we can write:

App.vue :

<template>
  <div id="app">
    <ValidationProvider name="email" rules="required" v-slot="{ errors }">
      <input v-model="email" type="text" placeholder="email">
      <span>{{ errors[0] }}</span>
    </ValidationProvider>

<input type="submit" value="submit">
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      email: ""
    };
  }
};
</script>

in our component and we’ll see a French validation message instead of an English one.

The is because we have:

localize("fr");

in main.js .

Localized Field Names

We can add field names by passing in an object to the localize method.

For example, we can write:

main.js

import Vue from "vue";
import App from "./App.vue";
import { localize, ValidationProvider, extend } from "vee-validate";
import { required } from "vee-validate/dist/rules";

localize({
  en: {
    names: {
      name: "name"
    }
  },
  fr: {
    names: {
      name: "nom"
    }
  }
});
localize("fr");
extend("required", required);

Vue.component("ValidationProvider", ValidationProvider);

Vue.config.productionTip = false;

new Vue({
  render: h => h(App)
}).$mount("#app");

We passed in an object to localize with the locale name as the key.

The names property is in each object.

Then we have the field names for each locale inside names .

Then we can write:

App.vue

<template>
  <div id="app">
    <ValidationProvider name="name" rules="required" v-slot="{ errors }">
      <input v-model="name" type="text" placeholder="name">
      <span>{{ errors[0] }}</span>
    </ValidationProvider>

<input type="submit" value="submit">
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      name: ""
    };
  }
};
</script>

Then we see ‘nom is not valid’ instead of ‘name is not valid’ because we set the locale to fr by writing:

localize("fr");

Custom Messages Per Field

We can add custom validation messages per field.

For instance, we can write:

import Vue from "vue";
import App from "./App.vue";
import { localize, ValidationProvider, extend } from "vee-validate";
import { required } from "vee-validate/dist/rules";

localize({
  en: {
    names: {
      name: "name"
    }
  },
  fr: {
    names: {
      name: "nom"
    },
    fields: {
      name: { required: "Le nom est requis" }
    }
  }
});
localize("fr");
extend("required", required);

Vue.component("ValidationProvider", ValidationProvider);

Vue.config.productionTip = false;

new Vue({
  render: h => h(App)
}).$mount("#app");

Then if we have the same code in App.vue , we have ‘Le nom est requis’ instead of an English message.

Conclusion

We can localize the validation messages in our code with Vee-Validate.

To do that, we can use the localize function that comes with Vee-Validate.

We can use the built-in messages or add our own messages.

Categories
Vue

Vee-Validate — Persisting Validation State and Nesting ValidationObservers

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.