0
votes

I'm having an issue using $emit in VueJS. I received this error:

[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "userAge"

Parent:

<template>
  <div class="component">
    <h1>The User Component</h1>
    <p>I'm an awesome User!</p>
    <button @click="changeName">Change my name</button>
    <p>Name is {{ name }}</p>
    <hr>
    <div class="row">
      <div class="col-xs-12 col-sm-6">
        <app-user-detail 
          :myName="name" 
          :resetNameParent="resetNameFn"
          @nameWasReset="name = $event"
          :userAge="age"
        ></app-user-detail>
      </div>
      <div class="col-xs-12 col-sm-6">
        <app-user-edit 
          :userAge="age"
          @ageWasEdited="age = $event"
        ></app-user-edit>
      </div>
    </div>
  </div>
</template>

<script>
import UserDetail from './UserDetail.vue';
import UserEdit from './UserEdit.vue';

export default {
  data() {
    return {
      name: 'Max',
      age: 27
    }
  },
  methods: {
    changeName() {
      this.name = 'Anna'
    },
    resetNameFn() {
      this.name = 'Max';
    },
  },
  components: {
    appUserDetail: UserDetail,
    appUserEdit: UserEdit
  }
}
</script>

<style scoped>
div {
  background-color: lightblue;
}
</style>

Child:

<template>
  <div class="component">
    <h3>You may edit the User here</h3>
    <p>Edit me!</p>
    <p>User Age: {{ userAge }}</p>
    <button @click="editAge">Edit Age</button>
  </div>
</template>

<script>
export default {
  props: ['userAge'],
  methods: {
    editAge() {
      // this.userAge = 30; - przestarzaƂe
      // this.$emit('ageWasEdited', this.userAge);  - przestarzaƂe
      this.$emit('ageWasEdited', 30);
    }
  }
}
</script>

<style scoped>
div {
  background-color: lightgreen;
}
</style>

I read the manual but I could not implement the solution correctly.

How can I avoid this error?

1
I don't think the code you've shared would give you that error. The line this.userAge = 30, is commented out, but would definitely result in that error. Is that the problematic line of code? Or are you saying you're seeing the error with the code you've shared as written? - thanksd
yes, I get such an error and it amazes me. This is STRING so it does not pass the pointer just creating a new variable in the pair console log - zielony10

1 Answers

0
votes

Any value passed as a prop is being "listened" on by the component that receives it. Thus, if the parent modifies this value, the child will have its own modified version overwritten with the parent's new value. This can lead to unexpected behavior. Better is to have a copy of the value any time it changes. For example, you could use a "watched" property:

{
    props: [ "userAge" ],
    data: function() {
        return {
            myUserAgeCopy: null
        };
    },
    watch: {
        userAge: function() {
            this.myUserAgeCopy = this.userAge;
        }
    }
}

Then all further operations are performed on myUserAgeCopy instead.


Note that the above still results in the same data overwriting problem as with mutating the prop directly. What's important is that now this overwriting is made explicit and predictable through the way you've written the code.


In general, if you need to keep the parent and child synchronized, you should always $emit any child-level changes, allow the parent to accept those changes, and then receive the $emitted changes in the child through the original prop:

child: {
    props: [ "userAge" ],
    data: function() {
        return {
            myUserAgeCopy: null
        };
    },
    watch: {
        userAge: function() {
            this.myUserAgeCopy = this.userAge;
        },
        myUserAgeCopy: function() {
            this.$emit('user_age_changed', this.myUserAgeCopy);
        }
    }
}

parent: {
    data: function() {
        return {
            userAge: 30
        };
    },
    methods: {
        handleUserAgeChange: function(userAge) {
            this.userAge = userAge;
        }
    }
}

<child :userAge="userAge" v-on:user_age_changed="handleUserAgeChange"/>