5
votes

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>
2
Well the advice is clear - do not change props passed to components inside of those - which you still do this.isChecked = !this.isChecked;. Also, why are you showing code commented out?connexo

2 Answers

1
votes

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.

3
votes

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>