Reputation: 3642
I'm creating a reusable checkbox component for vue and I've run into an interesting issue caused by vue's recycling of unused components.
This is best seen with an example:
Vue.component("checkbox", {
template: `
<div>
<slot></slot>:
<input type="checkbox" v-model="checked" v-on:change="updateVModel"/>
<span>changed!</span>
</div>
`,
props: {
value: {
type: Boolean,
required: true
}
},
data(){
return {checked: this.value};
},
watch: {
value(val){
this.changeAnimation();
this.checked = val;
}
},
methods: {
changeAnimation(){
let $span = this.$el.querySelector("span");
$span.classList.remove("animate");
setTimeout(() => $span.classList.add("animate"), 50);
},
updateVModel(){
this.changeAnimation();//this line is redundant since watch.value runs anyway
this.$emit("input", this.checked);
}
}
});
new Vue({
el: "#menu",
data: {
menu: 0,
checked0: true,
checked1: false
}
});
span{
transition: 1s;
opacity: 0;
}
span.animate{
animation: notice-me .3s ease-in-out 4 alternate
}
@keyframes notice-me{
0%{
opacity: 0;
}
100%{
opacity: 1;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="menu">
<div v-if="menu == 0">
<button v-on:click="menu = 1">Go to menu 1</button>
<h3>Menu 0</h3>
<checkbox v-model="checked0">checked0</checkbox>
<checkbox v-model="checked0">checked0</checkbox>
</div>
<div v-else>
<button v-on:click="menu = 0">Go to menu 0</button>
<h3>Menu 1</h3>
<checkbox v-model="checked1">checked1</checkbox>
<checkbox v-model="checked1">checked1</checkbox>
</div>
</div>
I've made my components as so whenever the value changes, either from a user click(with v-on:click
) or from the variable itself changing (with watch.value
), the word "changed!" blinks besides the checkbox a couple of times. Everything works fine, but the problem arises when we change menus using the "Change to menu" button and checked0 and checked1 are different values. "changed" will blink even though it shouldn't have.
This is obviously caused by vue recycling the components and using them for another variable. Since the variable's value is different from the old one, watch.value
is run, triggering the animation when we'd expect it not to happen.
I did a little bit of research and found that I could give all my different checkboxes vue keys like so: <checkbox v-model="checked1" key="thing1">checked1</checkbox>
, but I want to fix this elegantly and allow vue to recycle whatever it wants. There should be a way to detect if the value changed because it actually did or because of recycling.
So my question is how to I fix this problem, or how do I write my code differently to avoid it?
Upvotes: 0
Views: 416
Reputation: 43881
A key should really be associated with the conditionally-rendered (by v-for
or v-if
) unit. It should be bound to whatever is unique about the unit. In your case, you can use menu
:
<div v-if="menu == 0" :key="menu">
<button v-on:click="menu = 1">Go to menu 1</button>
<h3>Menu 0</h3>
<checkbox v-model="checked0">checked0</checkbox>
<checkbox v-model="checked0">checked0</checkbox>
</div>
<div v-else :key="menu">
<button v-on:click="menu = 0">Go to menu 0</button>
<h3>Menu 1</h3>
<checkbox v-model="checked1">checked1</checkbox>
<checkbox v-model="checked1">checked1</checkbox>
</div>
Upvotes: 1