Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoid mutating a prop : vuejs 2

Tags:

vue.js

vuejs2

Here what appears in the console when I run my code:"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: "isChecked"". I have seen what other posts say about it but i can't adapt it on my problem. Could someone explain it to me please?

PARENT: template:

<div class="checkBox-container">
<input type="checkbox"/>
<!-- <div class="check" :class="[size]" v-if="!isChecked"></div>
<div class="check" :class="[size]" v-else>
  <div>v</div>
</div> -->
<div class="check" :class="[size]" @click="changeVal">
  <div v-if="isChecked">v</div>
</div>
<div><label class="label">{{label}}</label></div>
<div><span class="subLabel">{{subLabel}}</span></div>

script:

export default {
  name: "ax-checkbox",
  props: {
     label: String,
    subLabel: String,
    size: String,
    isChecked: false,
    checks: []
  },
  methods: {
    changeVal() {
      this.isChecked = !this.isChecked;
      this.$emit("changeVal");
    }
  }
};

CHILD

<div class="filters">
    <ax-checkbox label="Où :" subLabel="Ville" size="small"></ax-checkbox>
    <div class="separator"></div>
    <ax-checkbox label="Quoi :" subLabel="Thématique(s)" size="small"></ax-checkbox>
    <div class="separator"></div>
    <ax-checkbox label="Quand :" subLabel="Dans..." size="small"></ax-checkbox>
</div>
like image 461
Alex Avatar asked Mar 01 '18 14:03

Alex


People also ask

How do I stop Vue mutating Props?

The answer is simple, you should break the direct prop mutation by assigning the value to some local component variables(could be data property, computed with getters, setters, or watchers). Here's a simple solution using the watcher. It's what I use to create any data input components and it works just fine.

Can you mutate Props?

Short answer is: NO. Long answer is: It will not work.

Can Props be modified Vue?

Two main characteristics of Vue props There are two specific things to keep in mind when dealing with props: Props are passed down the component tree to descendents (not up) Props are read-only and cannot be modified (as you may have discovered)

Is Vue prop reactive?

Props and data are both reactiveWith Vue you don't need to think all that much about when the component will update itself and render new changes to the screen. This is because Vue is reactive.


2 Answers

The prop you are mutating is isChecked.

So, create a local data variable (initialized it with isChecked) and mutate it instead:

export default {
  name: "ax-checkbox",
  props: {
     label: String,
    subLabel: String,
    size: String,
    isChecked: false,
    checks: []
  },
  data() {
    return {isCheckedInternal: this.isChecked}
  },
  methods: {
    changeVal() {
      this.isCheckedInternal = !this.isCheckedInternal;
      this.$emit("changeVal");
    }
  }
};

And replace it in the template:

<div class="checkBox-container">
<input type="checkbox"/>
<!-- <div class="check" :class="[size]" v-if="!isCheckedInternal"></div>
<div class="check" :class="[size]" v-else>
  <div>v</div>
</div> -->
<div class="check" :class="[size]" @click="changeVal">
  <div v-if="isCheckedInternal">v</div>
</div>
<div><label class="label">{{label}}</label></div>
<div><span class="subLabel">{{subLabel}}</span></div>


Note: The code above will use the prop isChecked only as initializer. If the parent changes in any way the value it passed to isChecked, the child component will not pick that change up. If you want to pick it up, add a watch in addition to the proposed code above:

  //...
  watch: {
    isChecked(newIsChecked) {
      this.isCheckedInternal = newIsChecked;
    }
  }
};

Ideal

There are some possible improvements to your code. Here are some suggestions:

  • subLabel prop should be sub-label
  • instead of emitting a changeVal value, emit update:isChecked and then you can use :is-checked.sync="myCheckedValue" in the parent.

This way you can still bind internally to the prop isChecked and not change it, but emit events and react to when the parent changes isChecked instead.

If you wanted to go the extra mile (and think it is worth it), you could also add a model option to your component, so you can be able to use v-model instead of :is-checked.sync.

See demo below.

Vue.component("ax-checkbox", {
  template: '#axCheckboxTemplate',
  props: {
    label: String,
    subLabel: String,
    size: String,
    isChecked: false,
    checks: []
  },
  model: {       // <== this part to will also enable v-model besides :is-checked.async
    prop: 'isChecked',
    event: 'update:isChecked'
  },
  methods: {
    updateIsChecked() {
      this.$emit("update:isChecked", !this.isChecked);
    }
  }
})
new Vue({
  el: '#app',
  data: {
    myCheckedValueSync: false, myCheckedValueVModel: false,
  }
});
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>

<template id="axCheckboxTemplate">
  <div class="checkBox-container">
    <input type="checkbox" :checked="isChecked" @change="updateIsChecked" />
    <div class="check" :class="[size]" @click="updateIsChecked">
      CLICK ME<div v-if="isChecked">v</div>
    </div>
    <label class="label">{{label}}</label><span class="subLabel">{{subLabel}}</span>
  </div>
</template>

<div id="app">
  <div class="filters">
    <pre>parent's myCheckedValueSync: {{ myCheckedValueSync }}</pre>
    <ax-checkbox label="Où :" sub-label="Ville" size="small" :is-checked.sync="myCheckedValueSync">
    </ax-checkbox>
      
    <pre>parent's myCheckedValueVModel: {{ myCheckedValueVModel }}</pre>
    <ax-checkbox label="Quoi :" sub-label="Thématique(s)" size="small" v-model="myCheckedValueVModel">
    </ax-checkbox>
  </div>
</div>
like image 114
acdcjunior Avatar answered Oct 15 '22 22:10

acdcjunior


As it appears you're trying to update the value of a property passed into your component, your component is in fact a custom input component. Take a look at the excellent Vue docs on this topic. I've summarised the idea below.

2-way databinding in Vue is handled by v-model. Applied to your isChecked property this comes down to v-model="isChecked", which is in fact syntactic sugar for :value="isChecked" @input="evt => isChecked = evt.target.value".

Thus for your component, you need to do the following:

  1. Update the name of the isChecked property to value

  2. In your changeVal method, emit an input event, like:

    changeVal() { this.$emit("input", !this.value); }

If need be, you could still also emit the changeVal event.

like image 36
thomaux Avatar answered Oct 15 '22 20:10

thomaux