Preventing the Overwriting of Parent Data by Updated Child Data in VueJS [2.6.14]

Currently utilizing vuejs 2.6.14 and encountering the following problem:

Changes made in child component are reflecting in parent component without using $emit in the code.

This is contrary to the typical scenario of updating data between parent and child components.

Below is a detailed overview of my code:

I have a parent component named Testing.vue, passing a JSON object ("userData") to a child component called GeneralData.vue.

This is what the parent component code looks like:

<template>
  <div id="testing-compo">
    <div style="margin-top: 1rem; margin-bottom: 1rem; max-width: 15rem">
          <label
            class="sr-only"
            for="inline-form-input-username"
            style="margin-top: 1rem; margin-bottom: 1rem"
            >Account settings for :</label
          >
          <b-form-input
            v-model="username"
            id="inline-form-input-username"
            placeholder="Username"
            :state="usernameIsValid"
          ></b-form-input>
        </div>
    <b-button class="button" variant="outline-primary" 
    @click="callFakeUser">
    Populate fake user
    </b-button>
    <GeneralData :userData="user" />
  </div>
</template>
<script>
export default {
  name: "Testing",
  components: {
    GeneralData,
  },
  data() {
    return {
      user: null,
      username: null,
    };
  },
  computed: {
    usernameIsValid: function () {
      if (this.username != null && this.username.length >= 4) {
        return true;
      } else if (this.username != null) {
        return false;
      }

      return null;
    },
  },
  methods: {
    async callFakeUser() {
      userServices.getFakeUser().then((res) => {
        this.user = res;
        console.log(this.user);
      });
    },
</script>

A straightforward testing component that calls userServices.getFakeUser(), which returns a JSON object asynchronously.

For the child component:

<template>
  <div id="general-compo">
    <!-- AGE -->
    <div class="mt-2">
      <label for="text-age">Age</label>
      <div>
        <b-form-input
          v-model="userAge"
          placeholder="+18 only"
          class="w-25 p-1"
          type="number"
        >
        </b-form-input>
      </div>
    </div>
    <!-- LANGUAGES -->
    <div class="mt-2">
      <label for="lang-list-id">Language(s)</label>
      <div
        v-for="langKey in userLangsCount"
        :key="langKey"
        style="display: flex; flex-direction: row"
      >
        <b-form-input
          readonly
          :placeholder="userLangs[langKey - 1]"
          style="max-width: 50%; margin-top: 0.5rem"
          disabled
        ></b-form-input>

        **This form is set to read only, for display purposes only**

        <b-button
          variant="outline-danger"
          @click="removeLang(langKey)"
          style="margin-top: 0.5rem; margin-left: 1rem"
          >Remove</b-button
        >

        **This button removes a language from the userLangs array by calling removeLang(langKey)**

      </div>
      <div style="display: flex; flex-direction: row">
        <b-form-input
          v-model="userCurrentLang"
          list="langlist-id"
          placeholder="Add Language"
          style="max-width: 50%; margin-top: 0.5rem"
        ></b-form-input>
        <datalist id="langlist-id">
          <option>Manual Option</option>
          <option v-for="lang in langList" :key="lang.name">
            {{ lang.name }}
          </option>
        </datalist>
        <b-button
          :disabled="addLangBtnDisabled"
          variant="outline-primary"
          @click="addLang()"
          style="margin-top: 0.5rem; margin-left: 1rem"
          >Add</b-button
        >
      </div>
    </div>
  </div>
</template>
<script>
import langList from "../assets/langList";
export default {
  name: "GeneralData",
  components: {},
  props: {
    userData: Object,
  },
  data() {
    return {
      userAge: null,
      langList: langList,
      userLangs: [],
      userCurrentLang: null,
    };
  },
  watch: {
    //Updating tabs with fetched values
    userData: function () {
      this.userLangs = this.userData.general.langs;
      this.userAge = this.userData.general.age
    },
  },
  computed:

    **userGeneral is supposed to represent the data equivalent of userData.general, it is therefore computed from the user input, its value is updated each time this.userAge or this.userLangs changes**
    userGeneral: function () {
      //user data in data() have been filled with userData values
      return {
        age: this.userAge,
        langs: this.userLangs,
      };
    },

**returns the amount of languages spoken by the user to display them in a v-for loop**
    userLangsCount: function () {
      if (this.userLangs) {
        return this.userLangs.length;
      }
      return 0;
    },

**gets a list of languages name from the original JSON list for display purposes**
    langNameList: function () {
      let namelist = [];
      for (let i = 0; i < this.langList.length; i++) {
        namelist.push(langList[i].name);
      }
      return namelist;
    },

**returns true or false depending on whether entered language is in original list**
    addLangBtnDisabled: function () {
      for (let i = 0; i < this.langList.length; i++) {
        if (this.userCurrentLang == langList[i].name) {
          return false;
        }
      }
      return true;
    },
  },
  methods: {
    addLang() {
      this.userLangs.push(this.userCurrentLang);
      this.userCurrentLang = null;
    },
    removeLang(key) {
      this.userLangs.splice(key - 1, 1);
    },
  }
}
</script>

Data displayed in the Vue.js dev tool after updating this.user in Testing.vue:

Data in Testing.vue :

user : {
 general{"age":22,"langs":["French"]}
}

Data in GeneralData.vue :

userData : {
  general:{"age":22,"langs":["French"]}
}

userAge : 22

userLangs : ["French"]

userGeneral : 
{
  general:{"age":22,"langs":["French"]}
}

Everything seems fine so far, right?

However, the issue arises when I change the age field in my form - userAge gets incremented, userGeneral.age gets updated, yet userData.general.age remains unchanged. This behavior is expected as userGeneral.age is computed based on this.userAge, and userData is a prop that should not be mutated according to best practices (and no method sets userData.general.age = xxx either).

Yet, if I click the Remove button next to French in the language list, this.userLangs gets updated as expected and becomes [], userGeneral.langs also gets updated to [], and surprisingly, userData.general.langs also gets updated to [] which doesn't make sense to me.

Even worse, in the parent component Testing.vue, user.general.langs is now also set to [].

Somehow, this.userLangs updated the prop this.userData, and this prop has updated its original sender user in the parent component, even though no $emit was used.

I do not want this behavior as it seems unintended and risky, and also because I plan to implement a 'save' button later to allow users to modify their values collectively.

Things I've tried: using various .prevent, .stop attributes on the @click element of the Remove/Add buttons, incorporating e.preventDefault in the methods, and modifying addLang and removeLang to include sending the $event parameter - none of these attempts resolved the issue.

Hopefully I didn't implement the .prevent part correctly, and someone can assist in preventing this unwanted reverse flow.

Answer №1

The issue at hand is that the lang variable is being passed as a reference, leading to mutations affecting the parent array. To prevent this, we should assign a copy of the original array instead of directly assigning it.

updateUserData: function () {       this.languages = [...this.userData.info.languages];       this.age = this.userData.info.age;     }

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

Issues with Angular displaying filter incorrectly

Whenever a user chooses a tag, I want to show only the posts that have that specific tag. For instance, if a user selects the '#C#' tag, only posts with this tag should be displayed. This is how my system is set up: I have an array of blogs that ...

"Upgrade your uploading experience with Fine Uploader 3.0 to easily customize PHP filenames post-upload

After uploading a file with a PHP script that changes the file name to an md5 value, I am having trouble retrieving the new file name. Fine Uploader, however, displays the original file name that was uploaded from the PC. I am looking for a way to retrieve ...

What is the best way to change the date format from "yyyy-MM-dd" in a JavaScript file to "dd/MM/yyyy" and "MM/dd/yyyy" formats in an HTML file?

Is there a way to transform the date string "2020-08-02" from a JavaScript file into the formats "08/02/2020" and "02/08/2020" in an html file, without relying on the new Date() function in JavaScript? I would greatly appreciate any assistance with this t ...

Dividing a U8IntArray in JavaScript

Looking to divide a U8IntArray that contains 20 bytes into two parts: the first 8 bytes and the remaining byte 9 to 20. var u8Array = new Uint8Array(20); var part1 = u8Array.subarray(0, 8); var part2 = u8Array.subarray(8); console.log(part1, part2); Th ...

Switch the dropdown menu to a button if it consists of only one option

I have been using a database within the Moodle Learning Management System that generates a bootstrap table. Here is an example of what it looks like: https://i.sstatic.net/UJc4M.png The last row in the table contains a dropdown menu. When viewing your ow ...

Is there a way to refresh a webpage without the need to reload it

Imagine a scenario where a tab on a website triggers the loading of a specific part of the webpage placed within a div. For example, clicking on a tab may trigger the loading of a file named "hive/index.php". Now, if a user selects an option from an auto ...

Importance of xpath:position and xpath:attribute

I'm currently developing a recording tool using JavaScript that is comparable to Selenium. When it comes to Playback, I require the XPath position and its attributes (shown in the screenshot below from Selenium). Can anyone provide guidance on how to ...

What is the best way to give this CSS button a "highlight" effect when it is clicked using either CSS3 or JavaScript?

In the HTML page, I have two buttons implemented with the following code: <!-- Button for WIFI projects: --> <a title="WIFI" href="javascript: void(0)" id="showWifi_${item.index}" class="showWifi"> <div class="news_box news_box_01 hvr-u ...

In order to display the user's identification when a button is clicked using Vue3, I have implemented a function called printName

<td class="one-third column" v-for="user in users" :key="user._id"> <div> <v-btn @click="printIdOrName(user)" height="50" size="large" co ...

Is the NPM package not being imported? How exactly is it being utilized?

mediacms-vjs-plugin is a unique plugin designed for Video.js. The MediaCmsVjsPlugin.js source code begins with: import { version as VERSION } from '../package.json'; import 'mediacms-vjs-plugin-font-icons/dist/mediacms-vjs-icons.css'; ...

What are the advantages of utilizing buffer geometries in Three.js?

I have experience using both BufferGeometry and Geometry, so I feel comfortable with either. Even when I need to make frequent modifications, I tend to lean on BufferGeometry because although the code is more verbose, it's not overly complex. Can you ...

Tips for utilizing 'toHaveClass' to find a partial match in Jest?

When I assign the class success to an element, React-Mui appends additional text to it in the DOM, such as mui-AbcXYZ-success. This causes my test using the code snippet below to fail: expect( getByTestId('thirdCheck')).toHaveClass("success ...

Updating Text in Textarea upon Selection Change

Need assistance with updating the content of a textarea based on a select option change. Below is an example of my code: <tr><td>Template:</td><td> <select t name="template" onChange = "setTemplate();"> <option ...

JQuery Falters in Responding to Button's Click Action

Forgive me for what may seem like a silly question, but as someone new to JQuery, I have been struggling to figure out why my function is not displaying an alert when the button is clicked. Despite thorough research on Google, I haven't been able to f ...

Establishing a website tailored specifically to each city's needs and interests, such as city1.example.com and city2

How can I create a website with subdomains dedicated to different cities like http://philly.example.com, http://maine.example.com, and http://sandiego.example.com? The majority of the site will remain the same in terms of layout, wording, database, and int ...

React unit tests experiencing issues with MSW integration

After creating a basic React application, I decided to configure MSW based on the instructions provided in order to set it up for unit tests in both node environment and browser. The main component of the app utilizes a custom hook called useFormSubmission ...

Creating see-through front walls in a 3D room using THREE.js

I am aiming to create a 3D room using three.js. My goal is to have the walls facing the camera rotate while becoming transparent. If you need an example, check out this link: http://jsfiddle.net/tp2f2oo4/ It appears that adding THREE.BackSide to the mate ...

myObject loop not functioning properly in Internet Explorer version 10

Could someone please point out what is wrong with this code snippet? HTML: <div id="res"></div> Javascript: var myObject = { "a" : { src : "someimagepath_a.png" }, "b" : { src : "someimagepath_b.png" }, }; va ...

Laravel triggers a 'required' error message when all fields have been filled

As I attempt to submit a form using an axios post request in laravel, I encounter an issue with the validation of the name and age fields, along with an image file upload. Here is a breakdown of the form structure: Below is the form setup: <form actio ...

How can I perform a mongoose request using express post method?

I need help with querying my MongoDB database using a form in HTML and a post method, it's a bit complicated... Below is the HTML code : <!DOCTYPE html> <html> <head> <link rel="stylesheet" href="interface.css" /> ...