A Tyshka
A Tyshka

Reputation: 4090

Vue Reactivity: Why replacing an object's property (array) does not trigger update

I have a Vue app where I'm trying to make a thin wrapper over the Mapbox API. I have a component which has some simple geojson data, and when that data is updated I want to call a render function on the map to update the map with that new data. A Vue watcher should be able to accomplish this. However, my watcher isn't called when the data changes and I suspect that this is one of the cases that vue reactivity can't catch. I'm aware that I can easily fix this problem using this.$set, but I'm curious as to why this isn't a reactive update, even though according to my understanding of the rules it should be. Here's the relevant data model:

data() {
  return{
    activeDestinationData: {
      type: "FeatureCollection",
      features: []
    }
  }
}

Then I have a watcher:

watch: {
  activeDestinationData(newValue) {
    console.log("Active destination updated");
    if (this.map && this.map.getSource("activeDestinations")) {
      this.map.getSource("activeDestinations").setData(newValue);
    }
  },
}

Finally, down in my app logic, I update the features on the activeDestination by completely reassigning the array to a new array with one item:

// Feature is a previously declared single feature
this.activeDestinationData.features = [feature];

For some reason the watcher is never called. I read about some of the reactivity "gotchas" here but neither of the two cases apply here:

Vue cannot detect the following changes to an array:

When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue

When you modify the length of the array, e.g. vm.items.length = newLength

What am I missing here that's causing the reactivity to not occur? And is my only option for intended behavior this.set() or is there a more elegant solution?

Upvotes: 1

Views: 2441

Answers (2)

Dima Vak
Dima Vak

Reputation: 619

If you need to watch a deep structure, you must write some params

watch: {
  activeDestinationData: {
      deep: true,
      handler() { /* code... */ }
}

You can read more there -> https://v2.vuejs.org/v2/api/#watch I hope I helped you :)

Upvotes: 1

buzatto
buzatto

Reputation: 10382

as default vue will do a shallow compare, and since you are mutating the array rather than replacing, its reference value is the same. you need to pass a new array reference when updating its content, or pass the option deep: true to look into nested values changes as:

watch: {
  activeDestinationData: {
    handler(newValue) {
      console.log("Active destination updated");
      if (this.map && this.map.getSource("activeDestinations")) {
        this.map.getSource("activeDestinations").setData(newValue);
      }
    },
    deep: true
  }
}

Upvotes: 4

Related Questions