0

I just noticed an unexpected behaviour and now I don't know if it is normal or not.

I have a component named follows and a child component named follow-list-modal

I'm passing a followList (pagination ) from follows to its child component follow-list-modal

In the follow-list-modal I store the paginated array in the variable members

Follows.vue
<template>
   <div>
         <follow-list-modal
           :follow-list="dataset">
          </follow-list-modal>
  
   </div>
</template>
<script>
export default {
props: {
   dataset: {
      type: Object,
      default: {},
    },
  },
}
</script>
FollowListModal.vue
<template>
   <div>
      <button @click="fetchMore"> More </button>
   </div>
</template>
<script>
export default {
props: {
   followList: {
      type: Object,
      default: {},
    },
data() {
    return {
      members: this.followList.data,
      dataset: this.followList,
    };
  },
methods: {
    fetchMore() {
            let nextPage = parseInt(this.dataset.current_page)  + 1;
            axios
              .get(this.dataset.path + '?page=' + nextPage)
              .then(({ data }) => this.refresh(data))
              .catch((error) => console.log(error));
           }
},
refresh(paginatedCollection) {
      this.dataset = paginatedCollection;
      this.members = this.members.concat(...paginatedCollection.data);
    },
}

When I click the button More in the follow-list-modal to get more data, I then want to append the new data to the members array.

The unexpected behaviour ( for me at least ). is that if I use push in the refresh method

this.members.push(..paginatedCollection.data); 

It appends data not only to members but also to followList which is data that comes from the parent component follows

But if I use concat instead of push, it appends data only to members variable and not to followList

this.members = this.members.concat(..paginatedCollection.data);

Is this behaviour normal ?

I don't get why the followList changes when the members variable changes, I thought that reactivity is one way.

In other words, the members changes when the followList changes, but not the other way around

P.S I don't emit any events from follow-list-modal to follows or change the data of the follows component in any way from the follow-list-modal

Orestis uRic
  • 347
  • 1
  • 6
  • 11
  • see the final note of this section in the Vue guide: https://vuejs.org/v2/guide/components-props.html#One-Way-Data-Flow – joakimriedel May 31 '21 at 12:55

2 Answers2

2

In JavaScript, the properties of an Object that are also Objects themselves, are passed by reference, and not by value. Or you might say that they are shallow copied.

Thus, in your example, this.members and this.followList.data are pointing to the same variable in memory.
So, if you mutate this.members, it will mutate this.followList.data as well.

You could avoid this by doing a deep copy of the objects. The easiest method, and arguably the fastest, would be to use JSON.parse(JSON.stringify(obj)), but look at this answer for more examples.

data() {
    return {
      members: [],
      dataset: [],
    };
},
created() {
    this.members = JSON.parse(JSON.stringify(this.followList.data));
    this.dataset = JSON.parse(JSON.stringify(this.followList));
}
Dev Catalin
  • 1,265
  • 11
  • 25
1

You instantiate your data with a direct link to the (initially undefined) property of your prop. This property is a complex entity like an Object (Arrays are Objects), and is thus called via reference. Since members references the same thing in memory as followList.data, when you're calling members, it will follow the reference to the same entity as followList.data. This doesn't have to do with Vue2 reactivity, but here's a link nontheless.

  • push mutates the array it is called on; it will follow the reference through members and change followList.data, updating its value when called through followList as well. Because the data key is not present on instantiation of the component, Vue can't watch it (just like you need to use Vue.set when adding a new key to a data object).
  • concat returns a new array of merged elements, and then replaces the reference in members with the new array. Therefore from this point on you'll no longer mutate followList.data, even with a push, as the reference has changed to a new entity.

When trying to set your initial members and dataset, I suggest using an initialization method that creates a clone of your followList and writes that to dataset, and running this on the created() or mounted() hook of your component lifecycle. Then create a computed property for members, no need to store followList.data thrice and potentially have dataset and members diverge.

tony19
  • 125,647
  • 18
  • 229
  • 307
Excalibaard
  • 1,903
  • 9
  • 19