Devon Bessemer
Devon Bessemer

Reputation: 35337

Computed object not updated using sync modifier

I have a computed property that simply formats the date:

computed: {
    items() { 
        return this.licenseItems.map(function(license) {
             license.expires_at = moment(license.expires_at).format('MM/DD/YYYY');
             return license;
        });
    }
}

I pass licenseItems to a form component with the .sync modifier and emit update:field event from the form. In vue dev tools, I can see that licenseItems (data) is properly updated, but items (computed) is still showing the old data so no re-computation was performed.

I have noticed that if I remove the map and just return the licenseItems object from the computed property, it is updated. Is there some issue with Vue's computed property on mapped objects when using the sync modifier?

Upvotes: 2

Views: 1801

Answers (1)

Roy J
Roy J

Reputation: 43881

You should be aware that you're modifying underlying objects in your computed. From your fiddle:

  computed: {
    mapped: function() {
      return this.items.map(function(item) {
        let original = item.date;
        item.date = moment(item.date).format('MM/DD/YYYY');
        console.log(original + ' => ' + item.date);
        return item;
      });
    }
  }

Your incrementDates function also modifies the underlying objects directly.

Because each element of items is an object, item is a reference to that same object, so your routine updates the members of items itself. If you intend to modify the object values, you should use a watch instead of a computed. If you want to have a proper computed that does not modify data items, you need to deep copy the objects.

In my example below, you can see that the data value and the computed value are distinct, but the computed is based on the data. Also, the update event works with sync to update the value in the parent, rather than the value being updated directly in the component. You can enter any format of date that Date understands to set the value.

new Vue({
  el: '#app',
  data: {
    licenseItems: [{
      expires_at: Date.now()
    }]
  },
  computed: {
    items() {
      return this.licenseItems.map(function(license) {
        const newItem = Vue.util.extend({}, license);

        newItem.expires_at = moment(license.expires_at).format('MM/DD/YYYY');
        return newItem;
      });
    }
  },
  components: {
    myUpdater: {
      props: ['items'],
      methods: {
        doUpdate(event, index) {
          const newObj = this.items.map(item => Vue.util.extend({}, item));
          
          newObj[index].expires_at = new Date(event.target.value);
          console.log("new object", newObj);
          this.$emit('update:items', newObj);
        }
      }
    }
  }
});
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.19.1/moment.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
  <my-updater :items.sync="licenseItems" inline-template>
    <div>
      <input v-for="item, index in items" :value="item.expires_at" @change="doUpdate($event, index)">
    </div>
  </my-updater>
  <div v-for="item in items">
    {{item.expires_at}}
  </div>
</div>

Upvotes: 2

Related Questions