WoJ
WoJ

Reputation: 29987

When is a computed property not reactive?

I have a Vue page which gathers information from external JSON sources, and then uses it to compute a property.

Problem: this property is not reactive (= it is not recomputed when underlying data changes)

<div id="app">
    <div v-for="tag in filteredTags">{{tag}}</div>
</div>

<script>
new Vue({
    el: "#app",
    data: {
        tags: {},
        allTags: {},
    },
    computed: {
        // provides an Array of tags which are selected
        filteredTags() {
            let t = Object.keys(this.allTags).filter(x => this.allTags[x])
            console.log(t)
            return t
        }
    },
    mounted() {
        // get source tags
        fetch("tags.json")
        .then(r => r.json())
        .then(r => {
            this.tags = r
            console.log(JSON.stringify(this.tags))
            // bootstrap a tags reference where all the tags are selected
            Object.keys(this.tags).forEach(t => {
                this.allTags[t] = true
            });
            console.log(JSON.stringify(this.allTags))
        })
    }
})
</script>

The file which is fetched ({"tag1":["/posts/premier/"],"post":["/posts/premier/","/posts/second/"]}) is correctly processed in mounted(), the output on the console is

{"tag1":["/posts/premier/"],"post":["/posts/premier/","/posts/second/"]}
{"tag1":true,"post":true}

filteredTags is however empty. On the console, I see it displayed (as []) right at the start of the processing of the page, which is initially fine (first compute, when allTags is empty), but is then not computed anymore when allTags changes (after tags.json is fetched, processed and allTags correctly updated).

Why isn't this reactive?

Upvotes: 1

Views: 499

Answers (1)

Hiws
Hiws

Reputation: 10334

Vue isn't reactive to properties that didn't exist when the object was added to data

Since your tag and allTags are empty objects with no properties (yet), any properties added after aren't reactive automatically.

To solve this you have to use the Vue.Set or this.$set functions provided by Vue. the Set function accepts the values this.$set(object, key, value)

new Vue({
  el: "#app",
  data: {
    tags: {},
    allTags: {},
  },
  computed: {
    // provides an Array of tags which are selected
    filteredTags() {
      let t = Object.keys(this.allTags).filter(x => this.allTags[x])
      return t
    }
  },
  mounted() {
    const r = '{"tag1":["/posts/premier/"],"post":["/posts/premier/","/posts/second/"]}';
    const rJson = JSON.parse(r);
    // You shouldn't need to use $set here as you replace the entire object, instead of adding properties
    this.tags = rJson;
    
    Object.keys(this.tags).forEach(t => {
      // Change this
      //this.allTags[t] = true;
      
      // To this
      this.$set(this.allTags, t, true);
    });
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>

<div id="app">
  <div v-for="tag in filteredTags">{{tag}}</div>
</div>

Upvotes: 3

Related Questions