0
votes

I have a checkbox component that tracks whether or not an item has been saved by the user as a favorite. This information is passed in as a prop.

Because we can't/shouldn't mutate props passed in from a parent component, I am using v-model on a computed property.

<template>
  <input class="favorite" type="checkbox" v-model="checked">
</template>
<script>
  module.exports = {
    props: ['favorite'],
    computed: {
      checked: {
        get: function getChecked() {
          return this.favorite;
        },
        set: function setChecked(newVal) {
          this.$emit('update:favorite', newVal);
        }
      }
    }
  };
</script>

The parent component controls sending requests to the favorites api & updating the state of each entity if/when the request is successful.

<template>
  <input-favorite
    @update:favorite="toggleFavorite" 
    :favorite="entity.favorite"
  ></input-favorite>
</template>
<script>
  module.exports = {
    methods: {
      toggleFavorite: function toggleFavorite(val) {
        if (val) {
          this.$store.dispatch('postFavorite', { id: this.entity.id, name: this.entity.name });
        } else {
          this.$store.dispatch('deleteFavorite', this.entity.id);
        }
      }
    }
  };
</script>

If the request fails, however, is it possible to prevent the checkbox from getting checked in the first place? Both this.favorite and this.checked stay in sync, but the state of the checkbox does not.

Because the data & props stay correct, I'm also having trouble figuring out how I could trigger a re-render of the checkbox to get it back to the correct state.

1
Note: your component doesn't accept entity as a prop, though you are passing it. Setting the checkbox value should just be a matter of setting entity.favorite appropriately when you detect that a request has failed. - Roy J
Typo in the question, not in the actual component. My apologies. entity.favorite is always correct. It starts out as entity.favorite = false. Even though the computed prop checked and the prop favorite (passed in from entity.favorite do not change, the checkbox is checked and I cannot reset it. - ebbishop

1 Answers

2
votes

I suspect the problem is that favorite never changes, so Vue doesn't see a need to update. You should update it to true upon receiving the checked value (so state is consistent) and then update it again to false when the request fails.

Vue.component('inputFavorite', {
  template: '#input-favorite',
  props: ['favorite'],
  computed: {
    checked: {
      get: function getChecked() {
        return this.favorite;
      },
      set: function setChecked(newVal) {
        this.$emit('update:favorite', newVal);
      }
    }
  }
});

new Vue({
  el: '#app',
  data: {
    entity: {
      favorite: false
    }
  },
  methods: {
    toggleFavorite: function toggleFavorite(val) {
      if (val) {
        console.log("Post");
        this.entity.favorite = true;
        // Mock up a failure
        setTimeout(() => {
          console.log("Failed");
          this.entity.favorite = false;
        }, 250);
      } else {
        console.log("Delete");
      }
    }
  }
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<template id="input-favorite">
  <input class="favorite" type="checkbox" v-model="checked">
</template>

<div id="app">
  <input-favorite @update:favorite="toggleFavorite" :favorite="entity.favorite"></input-favorite>
</div>

The way you have set this up lends itself to the recently-reintroduced .sync modifier, which would simplify your HTML a bit:

  <input-favorite :favorite.sync="entity.favorite"></input-favorite>

Then you do away with toggleFavorite and instead add a watch:

watch: {
  'entity.favorite': function (newValue) {
    console.log("Updated", newValue);
    if (newValue) {
      setTimeout(() => {
        console.log("Failed");
        this.entity.favorite = false;
      }, 250);
    }
  }
}