"VueJS: Difficulty in Ensuring Child Component Updates Along with Parent Component Changes

I am facing an issue with a Vue instance that passes an object to a child component. The child component contains a checkbox which, when clicked, triggers an event handled by the parent Vue instance to update the object passed to the child. According to the Vue documentation, this should update the related fields in the child component. However, I am encountering a problem where the date field is not updating as expected upon clicking the checkbox. In the image below, when I check the Management Name checkbox, I would anticipate seeing the current day displayed, but no date is shown. What could be the reason for this discrepancy?

Design

https://i.sstatic.net/EhnmS.png

Parent Instance

new Vue({
    el: "#evaluations-app",
    data: {
        evaluation: new Evaluation()        
    },
    methods: {        
        updateEmployeeSO: function (newSO, newSODate) {
            this.evaluation.EmployeeSO = newSO;
            this.evaluation.EmployeeSODate = newSODate;
        },
        updateReviewerSO: function (newSO, newSODate) {
            this.evaluation.ReviewerSO = newSO;
            this.evaluation.ReviewerSODate = newSODate;
        },
        updateManagementSO: function (newSO, newSODate) {
            this.evaluation.ManagementSO = newSO;
            this.evaluation.ManagementSODate = newSODate;
        }
});

Child Component

Vue.component('sign-off', {
    props: ['initEvaluation', 'perspective'],
    template: `
        <div class="sign-off-comp">
            <div class="sign-off-item">
                <div class="sign-off-field-1 col-1">{{evaluation.EmployeeName}}</div>
                <input :disabled="!enableEmployeeSO" v-model="evaluation.EmployeeSO" class="sign-off-field-2 col-2" type="checkbox" @click="EmployeeSOChanged"/>
                <div class="sign-off-field-3 col-3">{{employeeSODate}}</div>                
            </div>  
            <div class="sign-off-item">
                <div class="sign-off-field-1 col-1">{{evaluation.ReviewerName}}</div>
                <input :disabled="!enableReviewerSO" v-model="evaluation.ReviewerSO" class="sign-off-field-2 col-2" type="checkbox" @click="ReviewerSOChanged"/>
                <div class="sign-off-field-3 col-3">{{reviewerSODate}}</div>                
            </div>   
            <div class="sign-off-item">
                <div class="sign-off-field-1 col-1">{{evaluation.ManagementName}}</div>
                <input :disabled="!enableManagementSO" v-model="evaluation.ManagementSO" class="sign-off-field-2 col-2" type="checkbox" @click="ManagementSOChanged"/>
                <div class="sign-off-field-3 col-3">{{managementSODate}}</div>                
            </div>                   
        </div>
    `,
    data: function () {
        return {
            evaluation: this.initEvaluation,
            employeeClicked: false,
            reviewerClicked: false,
            managementClicked: false,
            currentCommentSource: this.perspective
        }
    },
    methods: {
        EmployeeSOChanged: function () {
            this.employeeClicked = true;
            //this.evaluation.EmployeeSODate == null || this.evaluation.EmployeeSODate == "" ? this.evaluation.EmployeeSODate = Helpers.getCurrentDate() : this.evaluation.EmployeeSODate = "";
            this.$emit('employee-so-changed', this.evaluation.EmployeeSO, this.evaluation.EmployeeSODate);
        },
        ReviewerSOChanged: function () {
            this.reviewerClicked = true;
            //this.evaluation.ReviewerSODate == null || this.evaluation.ReviewerSODate == "" ? this.evaluation.ReviewerSODate = Helpers.getCurrentDate() : this.evaluation.ReviewerSODate = "";
            this.$emit('reviewer-so-changed', this.evaluation.ReviewerSO, this.evaluation.ReviewerSODate);
        },
        ManagementSOChanged: function () {
            this.managementClicked = true;
            //this.evaluation.ManagementSODate == null || this.evaluation.ManagementSODate == "" ? this.evaluation.ManagementSODate = Helpers.getCurrentDate() : this.evaluation.ManagementSODate = "";
            this.$emit('management-so-changed', this.evaluation.ManagementSO, this.evaluation.ManagementSODate == null || this.evaluation.ManagementSODate == "" ? Helpers.getCurrentDate() : "");
        }
    },
    computed: {
        enableEmployeeSO: function () {
            return (this.perspective == "Employee" && !this.evaluation.EmployeeSO) || this.employeeClicked;
        },
        enableReviewerSO: function () {
            return (this.perspective == "Reviewer" && !this.evaluation.ReviewerSO && this.evaluation.EmployeeSO) || this.reviewerClicked;
        },
        enableManagementSO: function () {
            return (this.perspective == "Management" && !this.evaluation.ManagementSO && this.evaluation.ReviewerSO && this.evaluation.EmployeeSO) || this.managementClicked;
        },
        employeeSODate: function () {
            return this.evaluation.EmployeeSODate != null && this.evaluation.EmployeeSODate == new Date("01-01-1900") ? "" : this.evaluation.EmployeeSODate != null && this.evaluation.EmployeeSODate.length >= 10 ? this.evaluation.EmployeeSODate.substring(0, 10) : this.evaluation.EmployeeSODate;
        },
        reviewerSODate: function () {
            return this.evaluation.ReviewerSODate != null && this.evaluation.ReviewerSODate == new Date("01-01-1900") ? "" : this.evaluation.ReviewerSODate != null && this.evaluation.ReviewerSODate.length >= 10 ? this.evaluation.ReviewerSODate.substring(0, 10) : this.evaluation.ReviewerSODate;
        },
        managementSODate: function () {
            return this.evaluation.ManagementSODate != null && this.evaluation.ManagementSODate == new Date("01-01-1900") ? "" : this.evaluation.ManagementSODate != null && this.evaluation.ManagementSODate.length >= 10 ? this.evaluation.ManagementSODate.substring(0, 10) : this.evaluation.ManagementSODate;
        }
    }
});

Model

export class Evaluation {
    private _EmployeeName: string;
    private _EmployeeSO: boolean;
    private _EmployeeSODate: Date;
    private _ReviewerName: string;
    private _ReviewerSO: boolean;
    private _ReviewerSODate: Date;
    private _ManagementReviewerName: string;
    private _ManagementReviewerSO: boolean;
    private _ManagementReviewerSODate: Date;

    constructor() {
        this._EmployeeName = "";
        this._EmployeeSO = false;
        this._EmployeeSODate = new Date("01-01-1900");
        this._ReviewerName = "";
        this._ReviewerSO = false;
        this._ReviewerSODate = new Date("01-01-1900");
        this._ManagementReviewerName = "";
        this._ManagementReviewerSO = false;
        this._ManagementReviewerSODate = new Date("01-01-1900");
    }

    get EmployeeName(): string {
        return this._EmployeeName;
    }
    set EmployeeName(employeeName: string) {
        if (this._EmployeeName != employeeName) {
            this._EmployeeName = employeeName;
        }
    }
   
    get EmployeeSO(): boolean {
        return this._EmployeeSO;
    }
    set EmployeeSO(employeeSO: boolean) {
        if (this._EmployeeSO != employeeSO) {
            this._EmployeeSO = employeeSO;
        }
    }

    ...
}

Update

After realizing in my child component that I was using MangementSO and ManagementSODate while the model had ManagementReviewerSO and ManagementReviewerSODate, correcting this resolved the issue. However, I am unsure why putting props into the local data is considered incorrect based on the discussion below. Can someone provide clarification on this matter?

Answer №1

When it comes to initializing data properties with props, there is no issue at all. The main point of confusion in many of the comments here seems to stem from the fact that initEvaluation is an object. Due to this, any modifications made to that object will be seen throughout wherever the object is utilized.

In the code being discussed, both evaluation in the parent component and evaluation in the child component are actually referring to the same object. Therefore, any changes made to that object will directly impact both the parent and the child without triggering any complaints from Vue. This is because you are not technically altering the reference to the object; instead, you are just modifying its properties.

Typically, Vue will throw a warning if you pass a primitive value as a property. For instance, if you passed a string value and then tried using that value with v-model, Vue would raise a warning (in the development version) about mutating a property. This warning occurs because the value isn't relayed to the parent (which might be unexpected), and also because any alterations made in the parent's data will overwrite changes in the child.

If an object or array is passed as a property, Vue will only complain if you modify the object reference. For example, if in your code you were to do something like:

this.initEvaluation = new Evaluation()

Vue would warn you for mutating a property. Yet, in your code, simply changing the properties of initEvaluation does not trigger a warning, but rather ensures those values are reflected everywhere since it's the same object being updated universally.

As a result, setting evaluation: this.initEvaluation in your code is essentially redundant. You could use initEvaluation in your template and achieve the same outcome as using

evaluation</code, due to them referencing the same object. This concept aligns with what Luiz is trying to convey, despite a slight mislead in his initial statement. The data function is only called once, meaning data initialized with properties will only receive the property's value initially. However, since <code>initEvaluation
is an object, this detail doesn't hold significance in the context of the code in question. The object reference remains constant, only its properties change. If the parent decides to shift the reference for any reason, the child's evaluation won't update along with the new reference.

The idea of updates being instantly visible everywhere for an object may be debatable in terms of desirability. There are instances where you want to have control over when updates take place. In such scenarios, you might opt for something like

evaluation: Object.assign({}, this.initEvaluation)
, which generates a copy of the initEvaluation object. This approach allows you to freely make changes to the object within the child component without affecting components outside of it. Once you've validated and finalized the changes, you can then emit them accordingly.

Answer №2

By establishing the data properties based on the props provided (specifically, initEvaluation and perspective), they become independent of the props and only refer to the data itself, essentially acting as duplicates rather than direct references to the received props. Consequently, any changes in the props do not affect them.

If you are not modifying these properties within the component, it might be more efficient to directly access the props instead of creating separate data properties based on them.

However, considering that you are utilizing v-model on evaluation, referencing the props directly could impact the prop when modified.

In essence, it would be advisable to eliminate the following from data:

evaluation: this.initEvaluation
// ...
currentCommentSource: this.perspective

Instead, opt for direct reference to the props, and replace v-model with :value and @input, as per the recommended guidelines in the documentation.

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

Email sending error: Invalid username and password

Encountering this issue in Postman: { "code": "EAUTH", "response": "535-5.7.8 Username and Password not accepted. More information available at\n535 5.7.8 https://support.google.com/mail/ ...

Troubleshooting Confluence's "Hello World" Macro: Issues with NPM Installation and Startup

I am in the process of creating my very first confluence macro, and I am following the guidelines provided at this link: Right now, I am facing some difficulties with steps 2 and 4; specifically NPM install and NPM start: Encountered the following errors ...

Deciphering the outcome of the splice method in CoffeeScript

Recently, I have been utilizing CoffeeScript in conjunction with the JS splice function. From my understanding, the JS splice function should return the objects that were spliced out and modify the original array. While this concept works smoothly with sim ...

Utilizing Node.js createReadStream for Asynchronous File Reading

For a specific project, I developed a module that is responsible for splitting files based on the '\r\n' delimiter and then sending each line to an event listener in app.js. Below is a snippet of the code from this module. var fs = req ...

flexible side-by-side alignment - adaptable grids

I am currently working on a website project that involves a ul list with responsive columns. The column width adjusts based on the window size, and I aim to center-align the text within the li element according to the new column width. If you want to view ...

Automated validation and submission within an Adobe PDF document

After clicking the "submit" button on my PDF form, I want to perform a specific action (such as making a field readonly) based on whether the form validation and submission processes are successful: if (form.isValid()) { submitForm(...); if (form. ...

Eliminate the navigation bar option from a website template

Is there a way to permanently eliminate the menu button in this theme? Any guidance would be appreciated. Here's the link to the theme: https://github.com/vvalchev/creative-theme-jekyll-new ...

What is the best way to stop this Jquery slider from moving?

I've been struggling with this issue for what feels like forever, and it's driving me crazy! I have a slider on the homepage that I'm trying to enhance with a "click to pause" feature (and maybe even a click to resume, for good measure). I ...

Tips for implementing validation on dynamically inserted fields in a form

My form has two fields, and the submit button is disabled until those fields are filled with some text. However, if I add a third field dynamically using AngularJS, validation will not be supported for the new field. Check out the example here <butto ...

What is the correct way to set up Typescript for usage with Angular 2 in Visual Studio 2015?

I recently created an Angular 2 app using Visual Studio 2015 and TypeScript. I made sure to install TypeScript globally using the npm command "npm install -g [email protected]." However, when I try to build the project, I encounter several errors re ...

The system was unable to locate node.js with socket.io

I'm having trouble locating the file. According to the documentation I reviewed, socket.io is supposed to be automatically exposed. Encountered Error: polling-xhr.js?bd56:264 GET http://localhost:8081/socket.io/?EIO=3&transport=polling&t=LyY ...

I received no response when I checked my Discord messages

I'm currently working on creating a bot that will send a daily morning message at 9 o'clock with a customizable reaction. Right now, it's successfully sending the message on Discord but there seems to be an issue with the reaction part. Can ...

Tips for establishing a ref while utilizing React.cloneElement with a functional component

As I create my own Tooltip component, I encountered a particular issue - Function components are unable to receive any refs This is what my code currently looks like Here's the part of the Component that contains the Tooltip: <Tooltip title=&qu ...

What is the best way to display values from a Localstorage array in a tabular format using a looping structure

I have set up a local storage key 'fsubs' to store form submissions as an array. Here is how I am doing it: var fsubs = JSON.parse(localStorage.getItem('fsubs') || "[]"); var fcodes = {"barcodeno" : this.form.value.barcode, "reelno" : ...

How come @keyframes animations do not seem to cooperate with Vue.js?

I'm attempting to create a flashing link upon site load, but I'm encountering issues with the animation not working in Vue. Interestingly, when testing the same code without Vue in a separate file, it functions perfectly fine. Below is the spec ...

Error occurs while running NPM script

My current task involves updating the dependencies of a React application. One of the key scripts in this app is defined in the package.json file, which is responsible for generating a message bundle for each locale. "scripts": { "build:langs": "NODE_EN ...

PHP: How to Return a Multidimensional Array and Separate Variables Simultaneously

I am trying to send a 2D array with multiple individual variables from a PHP script to a JavaScript client using AJAX. Despite many attempts, I haven't been able to figure out how to include additional individual variables (like $var1, $var2, $var3) i ...

problems encountered when testing azure containerclient.listblobsbyhierarchy using sinon

I have developed a REST endpoint with the code "/files/lookup", which is designed to receive a query parameter folderPath and return a list of files with details, excluding content but including metadata. The endpoint connects to Azure Blob Stora ...

Exporting an Angular 2 component to use in a template

I am interested in creating a custom Tabs component that has the ability to display content from [root] within itself. It functions perfectly when using selectors in html tags (<tab1>), but there are instances where the selector is unknown. Since Tab ...

Extracting a button's attribute in Vue.js

Is there a way to retrieve the tblid attribute from a button element when a click event occurs? I've looked at similar questions on sites like this one, but haven't found a solution that works for me - see the example below. I find it odd that ...