Raphaël VO
Raphaël VO

Reputation: 2640

Vue doesn't update when computed data change

Context: I have a list of posts with tags, categories from wordpress api. I display these posts with Vue and using computed with a search box to filter the result based on titre, description, tags, and categories

Problem: I am trying to update a computed list when user click on a list of tag available. I add the get and set for computed data like this:

var vm = new Vue({
    el: '#blogs',       
    data: {
        search: '',
        posts: [],
        filterPosts: []
    },
    beforeMount: function() {
        // It should call the data and update
        callData();
    },
    computed: {
        filterPosts: {
            get: function() {
                var self = this;
                return self.posts.filter(function(post){
                    var query = self.search.toLowerCase();
                    var title = post.title.toLowerCase();
                    var content = post.content.toLowerCase();
                    var date = post.date.toLowerCase();
                    var categories = '';
                    post.categories.forEach(function(category) {
                       categories += category.name.toLowerCase(); 
                    });
                    var tags = '';
                    post.tags.forEach(function(tag){
                        tags += tag.name.toLowerCase();
                    });
                    return title.indexOf(query) !== -1 ||content.indexOf(query) !== -1 || date.indexOf(query) !== -1 || categories.indexOf(query) !== -1 || tags.indexOf(query) !== -1;
                });
            },
            set: function (newValue) {
                console.log(newValue);
                this.filterPosts = Object.assign({}, newValue);
            }
        }
    },
    methods: {
        filterByTag: function(tag, event) {
            event.preventDefault();
            var self = this;
            self.filterPosts = self.posts.filter(function(post){
                var tags = '';
                post.tags.forEach(function(tag){
                    tags += tag.name.toLowerCase();
                });
                return tags.indexOf(tag.toLowerCase()) !== -1;
            });
        }
    }
}); // Vue instance

The console.log always output new data based on the function I wrote on methods but Vue didn't re-render the view. I think I didn't do the right way or thought like Vue. Could you please give some insight?

Edit 1 Add full code.

I tried to add filterPosts in data but I received this error from Vue: The computed property "filterPosts" is already defined in data.

Upvotes: 1

Views: 10827

Answers (3)

pirs
pirs

Reputation: 2463

Try something like:

  data: {
    myValue: 'OK'
  },
  computed: {
    filterPosts: {
      get: function () {
        return this.myValue + ' is OK'
      }
      set: function (newValue) {
        this.myValue = newValue
      }
    }
  }

More: https://v2.vuejs.org/v2/guide/computed.html#Computed-Setter

Upvotes: 0

Corentin de Boisset
Corentin de Boisset

Reputation: 31

Your setter is actually not setting anything, it only logs the new value. You need to store it somewhere.

For example you can store it in the component's data:

data: {
  value: 'foo',
},
computed: {
  otherValue: {
    get() { /*...*/ },
    set(newVal) { this.value = newVal },
  },
},

But this is definitely not the only possibility, if you use Vuex, the setter can dispatch an action that will then make the computed value get updated. The component will eventually catch the update and show the new value.

computed: {
  value: {
    get() {
      return this.$store.getters.externalData;
    },
    set(newVal) {
      return this.$store.dispatch('modifyingAction', newVal);
    },
  },
},

The bottomline is you have to trigger a data change in the setter, otherwise your component will not be updated nor will it trigger any rerender.


EDIT (The original answer was updated with full code):

The answer is that unless you want to manually change the list filteredPosts without altering posts, you don't need a get and set function for your computed variable. The behaviour you want can be acheived with this:

const vm = new Vue({
  data() {
    return {
      search: '',
      posts: [], 
      // these should probably be props, or you won't be able to edit the list easily. The result is the same anyway.
    };
  },
  computed: {
    filteredPosts() {
      return this.posts.filter(function(post) {
        ... // do the filtering
      });
    },
  },
  template: "<ul><li v-for='post in filteredPosts'>{{ post.content }}</li></ul>",
});

This way, if you change the posts or the search variable in data, filteredPosts will get recomputed, and a re-render will be triggered.

Upvotes: 1

Rapha&#235;l VO
Rapha&#235;l VO

Reputation: 2640

After going around and around, I found a solution, I think it may be the right way with Vue now: Update the computed data through its dependencies properties or data.

The set method didn't work for this case so I add an activeTag in data, when I click on a tag, it will change the activeTag and notify the computed filterPost recheck and re-render. Please tell me if we have another way to update the computed data.

var vm = new Vue({
    el: '#blogs',       
    data: {
        search: '',
        posts: [],
        tags: [],
        activeTag: ''
    },
    beforeMount: function() {
        // It should call the data and update
        callData();
    },
    computed: {
        filterPosts: {
            get: function() {
                var self = this;
                return self.posts.filter(function(post){
                    var query = self.search.toLowerCase();
                    var title = post.title.toLowerCase();
                    var content = post.content.toLowerCase();
                    var date = post.date.toLowerCase();
                    var categories = '';
                    post.categories.forEach(function(category) {
                       categories += category.name.toLowerCase(); 
                    });
                    var tags = '';
                    post.tags.forEach(function(tag){
                        tags += tag.name.toLowerCase();
                    });
                    var activeTag = self.activeTag;
                    if (activeTag !== '') {
                        return tags.indexOf(activeTag.toLowerCase()) !== -1;
                    }else{
                        return title.indexOf(query) !== -1 ||content.indexOf(query) !== -1 || date.indexOf(query) !== -1 || categories.indexOf(query) !== -1 || tags.indexOf(query) !== -1;
                    }                       
                });
            },
            set: function (newValue) {
                console.log(newValue);
            }
        }
    },
    methods: {
        filterByTag: function(tag, event) {
            event.preventDefault();
            var self = this;
            self.activeTag = tag;
        }
    }
}); // Vue instance

Upvotes: 0

Related Questions