Validating Dynamically Displayed Inputs

A common case is to use v-if to display a field or two based on some sort of criteria, for example you could display a state field if the user is from USA and hide it if its not applicable. or generate a list of inputs from JSON data using v-for. This is completely supported by vee-validate but there is a caveat to look out for.

A little bit of background, here is a quote from Vue docs on the input being reused:

Vue tries to render elements as efficiently as possible, often re-using them instead of rendering from scratch. Beyond helping make Vue very fast, this can have some useful advantages.

The official example uses the following template:

<template v-if="loginType === 'username'">
  <label>Username</label>
  <input placeholder="Enter your username">
</template>
<template v-else>
  <label>Email</label>
  <input placeholder="Enter your email address">
</template>

Handling v-if

When attempting to validate those inputs, it would seem that vee-validate thinks they are the same one depending on who got rendered initially. This is due to Vue actually re-using the input, so to vee-validate and due to some limitation in the directive API, vee-validate doesn't know that the input got switched. So you need to help it a little bit by using key attribute on the inputs which forces Vue to render those elements independently.

<template v-if="loginType === 'username'">
  <label>Username</label>
  <input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
  <label>Email</label>
  <input placeholder="Enter your email address" key="email-input">
</template>

Validating those fields now will behave as expected, just remember that the key attribute should be unique to its respective field.

Handling v-for

Using indexes in v-for as the key is not sufficient as adding/removing items will make some fields assume others' keys. So the following wouldn't work in a scenario where fields are added/removed by the user interaction.

  <div v-for="(input, idx) in inputs" :key="idx">
    <label>Username</label>
    <input placeholder="Enter your username" key="username-input">
  </div>

To fix this, try to generate a unique id for each key in the loop:

  <div v-for="input in inputs" :key="input.id">
    <label>Username</label>
    <input placeholder="Enter your username" key="username-input">
  </div>

Where a basic id generator would look like this:

let id = 0;

export default {
  data: () => ({
    inputs: []
  }),
  methods: {
    addInput () {
      this.inputs.push({
        id: id,
        value: null
      });
      // increment the id for the next input that will be created.
      id++;
    }
  }
};

This ensures each created field is completely separate and Vue shouldn't try to re-use them.

TIP

You may actually want Vue to re-use the inputs, make sure you are handling the key attribute value correctly for your case.