Skip to content
Advertisement

change child-component checked state from parent-component synchronously fail

Please run the worked demo:

let myCheckbox = Vue.component('my-checkbox', {
  template: `<div>
                <input type="checkbox" id="check1" :checked="checked" @change="change">{{checked}}            
            </div>`,
  props: ['checked'],
  methods: {
    change() {
      this.$emit('change', event.target.checked);
    }
  }
})

new Vue({
  el: '#app',
  data: {
    checked: true,
    count: 1
  },
  methods: {
    change(isChecked) {
      this.count++
        this.checked = isChecked
      if (this.count % 2 === 0) {
        // this.checked = !isChecked
        setTimeout(() => {
          this.checked = !isChecked
        }, 10);
      }
    }
  },
  components: {
    myCheckbox
  }
})
span {
  background: pink;
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
  <my-checkbox :checked="checked" @change="change"></my-checkbox>
  When
  <span>{{count}}</span> is even your check will fail
</div>

however, if i change the synchronous operation to asynchronous operation in change which means change

        setTimeout(() => {
          this.checked = !isChecked
        }, 10);

to

         this.checked = !isChecked

the demo is not going work because checked in the check1 is not updated.

My question is

I don’t know why synchronous operation is not work!

I guess there is a promise after the change to do the underlying operation and that’s why setTimeout works.However, I am not sure and I didn’t find any explanation about that.

Advertisement

Answer

When you tick the checkbox, the (native DOM) checkbox changes its appearance by adding the v (tick symbol).

When that tick happens, it emits the change event.

Notice that, at this point, the checked property from Vue has not been changed yet. This change will happen when “someone” picks up the change event and sets the checked accordingly. That’s what the line 4 below does:

methods: {                      // 1
  change(isChecked) {           // 2
    this.count++;               // 3
    this.checked = isChecked    // 4

Now, when the this.count is even, in the code without setTimeout, you set right away this.checked to its previous value.

So, since Vue sees that this.checked did not change after the execution of the method, Vue understands there is no updating (no repainting) to do. That’s why the ticked symbol (v above) remains (wrongly) ticked: Vue did not repaint the component.

As a proof, try the demo below. Notice the updated lifecycle handler never executes:

let myCheckbox = Vue.component('my-checkbox', {
  template:
  `<div>
     <input type="checkbox" id="check1" :checked="checked" @change="change">{{checked}}            
   </div>`,
  props: ['checked'],
  methods: {
    change() {
      this.$emit('change', event.target.checked);
    }
  },
  // not executed because `checked` never changes (is always true)
  updated() { console.log('my-checkbox updated') }
})

new Vue({
  el: '#app',
  data: {
    checked: true,
    count: 1
  },
  methods: {
    change(isChecked) {
      this.count++;
      this.checked = isChecked
      if (this.count % 2 === 0) {
        this.checked = !isChecked
      }
    }
  },
  components: {
    myCheckbox
  }
})
span {
  background: pink;
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
  <my-checkbox :checked="checked" @change="change"></my-checkbox>
  When
  <span>{{count}}</span> is even your check will fail. - checked: <span>{{checked}}</span> - it never changes, always <b>true</b>, so my-checkbox <b>never</b> has to be updated/repainted.
</div>

Now, when you use the setTimeout, at the moment the change(isChecked) method has ended its execution, the this.checked has changed! Which triggers the repaint.

The thing is, shortly after the setTimeout handler executes, setting this.checked back to its original value. This triggers another repaint. Notice in the demo below, the updated hook is executed twice when this.count is even.

Finally, as a last note, the idiomatic way of doing that in Vue is using Vue.nextTick(), not a setTimeout:

let myCheckbox = Vue.component('my-checkbox', {
  template:
  `<div>
     <input type="checkbox" id="check1" :checked="checked" @change="change">{{checked}}            
   </div>`,
  props: ['checked'],
  methods: {
    change() {
      this.$emit('change', event.target.checked);
    }
  },
  updated() { console.log('my-checkbox updated') },
})

new Vue({
  el: '#app',
  data: {
    checked: true,
    count: 1
  },
  methods: {
    change(isChecked) {
      this.count++;
      this.checked = isChecked
      if (this.count % 2 === 0) {
        Vue.nextTick(() => {
          this.checked = !isChecked
        });
        //setTimeout(() => {
        //  this.checked = !isChecked
        //}, 10);
      }
    }
  },
  components: {
    myCheckbox
  }
})
span {
  background: pink;
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
  <my-checkbox :checked="checked" @change="change"></my-checkbox>
  When
  <span>{{count}}</span> is even the checked will be overridden. checked: <span>{{checked}}</span>
</div>
User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement