javascriptarraysvue.js

Changing the element of an array created from the props inside the child component also affect the parent component's props | Vuejs 3


I have a parent component(App) in which I have an array of objects. I passed this array as a prop to the child component(UserLocations). Now inside the child component, I use this array and create a data variable.

So If I am going to change the one element of the array then why the parent component's property is also changed.

App.Vue

<template>
  <user-locations :initalLocations="locations"/>
</template>

<script>
import UserLocations from './components/UserLocations.vue'

export default {
  name: 'App',
  components: {
    UserLocations
  },
  mounted() {
  },
  data: function() {
    return {
      locations: [
        {"id": 121, name: "test 121", "order": 1},
        {"id": 122, name: "test 122", "order": 2},
        {"id": 123, name: "test 123", "order": 3},
        {"id": 124, name: "test 124", "order": 4}
      ]
    }
  }
}
</script>

UserLocations.vue

<template>
    <ul>
        <li v-for="(location) in locations"
        :key="location.id"
        > 
        <span @click="decreaseOrder(location)">Up</span>
        {{ location.name}} {{location.order}}
        <span @click="increaseOrder(location)">down</span>
        </li>
    </ul>
</template>
<script>

export default {
    data: function() {
        return {
            locations: [...this.initalLocations]
        }
    },
    props: {
        initalLocations: { 
            type: Array,
        },
    },
    // computed: {
    //     locations() {
    //         return [
    //             ...this.initalLocations
    //         ]
    //     }
    // },
    methods:{
        increaseOrder(location) {
            if (location.order != this.locations.length) {
                this.locations = this.locations.map(l => {
                    var returnLocation = {...l};
                    if (l.id == location.id) {
                        l.order += 1
                    }
                    return returnLocation
                });
            }
        },
        decreaseOrder(location) {
            if (location.order != 1) {
                this.locations = this.locations.map(l => {
                    var returnLocation = {...l};
                    if (l.id == location.id) {
                        l.order -= 1
                    }
                    return returnLocation
                });
            }
        },
    }
}
</script>

As you can see, I use the initalLocations props to make the location prop inside UserLocations component and when I change one of the array's objects by click on either the up/down button, It changes the prop passed to the UserLocations instead of changing the local data "locations"


Solution

  • The spread operator doesn't clone deeply the array, you need a function that clone the array instead of referencing it :

    <script>
    function deepCopy(obj) {
        if (typeof obj !== 'object' || obj === null) {
            return obj;
        }
    
    
        if (obj instanceof Array) {
            return obj.reduce((arr, item, i) => {
                arr[i] = deepCopy(item);
                return arr;
            }, []);
        }
    
        if (obj instanceof Object) {
            return Object.keys(obj).reduce((newObj, key) => {
                newObj[key] = deepCopy(obj[key]);
                return newObj;
            }, {})
        }
    }
    
    export default {
        data: function() {
            return {
                locations: deepCopy(this.initalLocations)
            }
        },
    ...