Changes in a deep copy of an object in a child component are directly reflected in the parent object in VueJS

Let's begin by discussing the layout. I have a page dedicated to showcasing information about a specific company, with a component Classification.vue. This component displays categories of labels and the actual labels assigned to the current company. Initially, I fetch all possible categories using an axios get request, followed by retrieving all labels assigned to the current company. Then, I map the labels to their respective categories within the component Classification.vue:

import DoughnutChart from "@comp/Charts/DoughnutChart";
import ModalDialog from '@comp/ModalDialog/ModalDialog';
const EditForm = () => import('./EditForm');

export default {
    components: {
        DoughnutChart, ModalDialog, EditForm
    },
    props: ['companyData'],
    async created() {
        const companyLabels = await this.$axios.get('/companies/' + this.companyData.id + '/labels');
        const allLabelsCategories = await this.$axios.get('/labels/categories');
        allLabelsCategories.data.map(cat => {
            this.$set(this.labelsCategories, cat.labelCategoryId, {...cat});
            this.$set(this.labelsCategories[cat.labelCategoryId], 'chosenLabels', []);
        });
        companyLabels.data.map(l => {
            this.labelsCategories[l.label.labelCategory.labelCategoryId].chosenLabels.push({...l.label, percentage: l.percentage})
        });
    },
    computed: {
        portfolioChartData() {
            let portfolios = [];
            // 35 id stands for 'Portfolio' labels category
            if (this.labelsCategories[35] !== undefined && this.labelsCategories[35].chosenLabels !== undefined) {
                this.labelsCategories[35].chosenLabels.map(label => {
                    portfolios.push({label: label.name, value: label.percentage});
                });
            }
            return portfolios;
        },
        portfolioLabels() {
            let portfolios = [];
            // 35 id stands for Portfolio labels category
            if (this.labelsCategories[35] !== undefined && this.labelsCategories[35].chosenLabels !== undefined) {
                return this.labelsCategories[35].chosenLabels;
            }
            return portfolios;
        }
    },
    data() {
        return {
            labelsCategories: {}
        }
    }
}

Everything seems to be working smoothly so far. The object labelsCategories contains keys that are category IDs and values that are category objects with the added key chosenLabels, which we set up in the created() method. Computed properties are utilized for the 'Portfolio' category chart. The use of $set in the created() method triggers reactivity in the labelsCategories object, allowing computed properties to react accordingly. Now, a new component within Classification.vue - EditForm.vue is dynamically imported. In this component, a similar process is carried out, however, all possible labels for every category need to be retrieved, not just the assigned ones. To achieve this, a prop is passed as follows:

<modal-dialog :is-visible="isFormActive" @hideModal="isFormActive = false">
       <EditForm v-if="isFormActive" ref="editForm" :labels-categories-prop="{...labelsCategories}" />
</modal-dialog>

The EditForm component is structured like this:

export default {
    name: "EditForm",
    props: {
        labelsCategoriesProp: {
            type: Object,
            required: true,
            default: () => ({})
        }
    },
    created() {
        this.labelsCategories = Object.assign({}, this.labelsCategoriesProp);
    },
    async mounted() {
        let labels = await this.$axios.get('/labels/list');
        labels.data.map(label => {
            if (this.labelsCategories[label.labelCategoryId].labels === undefined) {
                this.$set(this.labelsCategories[label.labelCategoryId], 'labels', []);
            }
            this.labelsCategories[label.labelCategoryId].labels.push({...label});
        });
    },
    data() {
        return {
            labelsCategories: {}
        }
    }
}

And now, the issue arises. Whenever the modal window containing the EditFrom component is opened, the computed properties from Calssification.vue are triggered causing the chart to animate and change its data. Why is this happening? After investigating further, it was discovered that the use of $set in the EditForm component also impacts the parent component (Classification.vue). How is this even possible? Despite attempting to pass the prop as {...labelsCategories} and using

this.labelsCategorie = Object.assign({}, this.labelsCategoriesProp);
, changes made in the child component affect the parent. A direct comparison between the prop and labelsCategories objects within the EditForm component through === and 'Object.is()' reveals they are not identical, leading to confusion on my end. Any assistance in resolving this matter would be greatly appreciated. By the way, one workaround I found is passing the prop as
:labels-categories-prop="JSON.parse(JSON.stringify(labelsCategories))"
, although it feels somewhat like a hack.

Answer №1

After delving deeper into this issue, I discovered that both {...labelsCategories} and

Object.assign({}, this.labelsCategoriesProp)
only create shallow copies of an object, not deep copies. This realization appeared to be the root cause of the problem I was facing. I came across an enlightening article discussing shallow and deep copying of objects: https://medium.com/javascript-in-plain-english/how-to-deep-copy-objects-and-arrays-in-javascript-7c911359b089

Therefore, I have the option to stick with my workaround using

JSON.parse(JSON.stringify(labelsCategories))
, utilize a library like lodash:

_.cloneDeep(labelsCategories)

Or as suggested in the article, I can create a custom method. This choice seemed fitting for me, so I incorporated a deepCopy() function into my existing Vue mixin for handling objects:

deepCopy(obj) {
    let outObject, value, key;

    if (typeof obj !== "object" || obj === null) {
        return obj; // Return the value if obj is not an object
    }

    // Create an array or object to hold the values
    outObject = Array.isArray(obj) ? [] : {};

    for (key in obj) {
        value = obj[key];
        // Recursively (deep) copy for nested objects, including arrays
        outObject[key] = this.deepCopy(value);
    }

    return outObject;
},     

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Adjust the size at the location of the cursor

I am having trouble understanding how to implement zoom based on mouse position using this example here: (https://stackblitz.com/edit/js-fxnmkm?file=index.js) let node, scale = 1, posX = 0, posY = 0, node = document.querySelector('.f ...

Transfer a variable from one PHP page to another and navigate between the two pages

I am just starting to learn PHP and I have a scenario where I need to retrieve the last ID from the database. For each ID, I need to fetch the state and the associated link. If the state is equal to 1, then I need to extract some content from the link (whi ...

The Iron Seal feature is ineffective when a user tries to log in

Iron.seal isn't properly updating the npm module Iron, which is causing login issues for users. var obj = { a: 1, b: 2, c: [3, 4, 5], d: { e: 'f' } }; var password = 'some_not_random_password_that_is_at_lea ...

Jumping Iframe Anchor Link in Src

I'm facing a challenge with an iframe positioned in the center of a webpage. I want to show content from another page within the iframe, but not starting at the very top. To achieve this, I inserted an anchor into the src of my iframe, linked to an a ...

Tips on resolving the Warning message: "The event handler property `onExited` is a known property in StatusSnackbar component, but it will

When using the StatusSnackbar component, I encountered a warning about a known event handler property onExited. How can I resolve this issue? Component: import Snackbar from '@material-ui/core/Snackbar' import { withStyles } from '@material ...

Issue with jQuery validation plugin is that the select element is not being properly validated

The jQuery validation plugin I'm using (http://bassistance.de/jquery-plugins/jquery-plugin-validation/) is functioning properly on most elements, but there are 2 select boxes where it's not working. You can see an example of one of these problema ...

What is the best way to calculate the total of all files within a folder structure using JavaScript

Here is the array I'm working with: Array (7812) 0 {foldername: "", amount_of_files: 12, children: 86} 1 {foldername: "/pm", amount_of_files: 3, children: 7} 2 {foldername: "/pm/css", amount_of_files: 1, children: 0} 3 {f ...

Create an AngularJS Directive that will display an array of n elements in the form of m rows of tables

Challenge: I have an array of 'n' items that I need to display in separate tables, with each table containing m rows. For instance, if n=30 and m=6, then I should have 5 tables each with 6 items displayed horizontally. Proposed Solution: I attem ...

"Exploring the world of JavaScript through the lens of time

This summer, I have the opportunity to assist a friend with some JavaScript challenges on his website. The main issue seems to revolve around technical difficulties with online form submissions. Unfortunately, there are instances where we struggle to verif ...

Utilizing an Array of objects in conjunction with $.when

I have a list of Ajax requests stored in an Array, and I need to wait for all of them to finish loading before processing the results. Here is the code snippet I am currently using: $.when( RequestArray ).done(function(){ this.processResu ...

Are there any conventional methods for modifying a map within an Aerospike list?

Attempting to modify an object in a list using this approach failed const { bins: data } = await client.get(key); // { array: [{ variable: 1 }, { variable: 2 }] } const { array } = await client.operate(key, [Aerospike.maps.put('array', 3).withCon ...

Changing the input programmatically does not trigger an update in the Angular model

I am currently facing a challenge where I have a text input that is connected to a model value in my application. However, I am struggling to programmatically change the input value and ensure that this change reflects in the model. My understanding is th ...

Establishing a restricted channel exclusive to a specific role for joining

I want to create a special channel where only members from the partyInvitees collection (Collection<Snowflake, GuildMember>) can join. const permission = Discord.Permissions.FLAGS; message.guild.createRole({ name: message.author.username + "&apos ...

Could you walk me through the details of this React function?

Currently, I have a function in place that retrieves products from the database to display on the eCommerce website's product page. Now, I am working on creating a similar function for user sign-in. Could you lend me a hand with this? I'm still ...

Prevent form submission with jQuery during validation process

Currently, I am working on validating a form using jQuery. My main objective now is to have the submit button disabled until all fields are correctly filled in. To achieve this, I have implemented the following approach: http://jsfiddle.net/w57hq430/ < ...

Nextjs couldn't locate the requested page

After creating a new Next.js application, I haven't made any changes to the code yet. However, when I try to run "npm run dev," it shows me the message "ready started server on [::]:3000, url: http://localhost:3000." But when I attempt to access it, I ...

How can async/await help in retrieving the value of a CORS request?

Trying to make a CORS request and utilize async/await to extract the value from it (using jquery). The functions createCORSRequest and executeCORSRequest seem to be functioning correctly, so the implementation details are not my main concern. The function ...

Dynamic data series updates in Vue ApexCharts

Is there a way to dynamically update data in an ApexCharts series? I have developed a Vue Component utilizing ApexCharts. This component receives updates from its parent where multiple instances are present. The updated values are passed through props. < ...

Creating a custom progress bar using Javascript and Jquery

I developed a progress bar that is fully functional. Here is the HTML structure: <div class="progress"> <div class="progress-bar progress-bar-striped active" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" style ...

Passing values dynamically to a JavaScript function within a Chrome extension using Python at runtime

I have encountered a unique challenge in my automation work, which I detailed in this query It's important to note that I am utilizing Python with Selenium WebDriver As I navigate through a series of changes and obstacles, I find myself exploring th ...