Search code examples
javascriptvue.jsvuelidate

Vue bootstrap b-form-select sets vuelidate's $anyDirty to true on load


The issue I'm having is due to the input event that's being run when I first set a value to the v-model, this data is being loaded in via an API; I understand why the form is being set to dirty (as this it is being changed) but this causes problems in another component I have which checks if the $v.$anyDirty flag is sets to true and if it is, creates a pop up to say "are you sure you want to navigate away" but calling $v.reset() after the data is loaded doesn't work.

Vue.use(vuelidate.default);
const { required } = window.validators;
new Vue({
  el: "#app",
  data: {
    todos: [],
    todo: ""
  },
  async created() {
    var data = await axios.get("https://jsonplaceholder.typicode.com/todos");
    this.todos = data.data.map(d => d.id);
    this.todo = this.todos[0];
    this.$v.$reset()
  },
  validations() {
    return {
      todo: { required }
    };
  }
});
<link href="https://unpkg.com/[email protected]/dist/bootstrap-vue.css" rel="stylesheet"/>
<link href="https://unpkg.com/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"/>

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/[email protected]/dist/bootstrap-vue.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/validators.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/vuelidate.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.js"></script>



<div id='app'>
  <div class="row">
    <div class="col-md-4">
      <b-form-select v-model="$v.todo.$model" :options="todos"></b-form-select>
    </div>
    <div class="col-md-8">
      <code>
        $anyDirty: {{$v.$anyDirty}}
      </code>
    </div>
  </div>
</div>


Solution

  • The problem is that $v.reset() is being run before vue renders so the input events happen after, so the stack trace would look like this

    load > set values > reset validation > render > input event

    You need to put the reset inside Vue.nextTick and then it'll work as it'll change the execution to be

    load > set values > render > input event > reset validation

    Vue.use(vuelidate.default);
    const {
      required
    } = window.validators;
    new Vue({
      el: "#app",
      data: {
        todos: [],
        todo: ""
      },
      async created() {
        var data = await axios.get("https://jsonplaceholder.typicode.com/todos");
        this.todos = data.data.map(d => d.id);
        this.todo = this.todos[0];
        Vue.nextTick(() => {
          this.$v.$reset()
        })
      },
      validations() {
        return {
          todo: {
            required
          }
        };
      }
    });
    <link href="https://unpkg.com/[email protected]/dist/bootstrap-vue.css" rel="stylesheet" />
    <link href="https://unpkg.com/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" />
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/bootstrap-vue.min.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/validators.min.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/vuelidate.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.js"></script>
    
    
    
    <div id='app'>
      <div class="row">
        <div class="col-md-4">
          <b-form-select v-model="$v.todo.$model" :options="todos"></b-form-select>
        </div>
        <div class="col-md-8">
          <code>
            $anyDirty: {{$v.$anyDirty}}
          </code>
        </div>
      </div>
    </div>

    As a note, you can also call Vue.nextTick with this.$nextTick