First let me add an example that replicates the issue you are describing.
console.clear()
function debounce(func, wait, immediate) {
var timeout;
return function() {
console.log("Called from component ", this._uid)
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
Vue.component("doesntwork",{
props:["value"],
template:`<div>Component #{{_uid}} Value: {{innerValue}}</div>`,
data(){
return {
innerValue: this.value
}
},
watch:{
value(newVal){
this.processData(newVal)
}
},
methods:{
processData: debounce(function(newVal){
this.innerValue = newVal
}, 1000)
},
})
new Vue({
el: "#app",
data:{
parentValue: null,
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<script src="https://unpkg.com/[email protected]"></script>
<div id="app">
Type some text. Wait one second. Only the *last* component is updated.<br>
<input type="text" v-model="parentValue">
<doesntwork :value="parentValue"></doesntwork>
<doesntwork :value="parentValue"></doesntwork>
<doesntwork :value="parentValue"></doesntwork>
</div>
Essentially what is going on here is that the debounced function is created when the component is compiled, and each instance of the component shares the same debounced function. The context of this
will be different in each one, but it is the same function. I added a console.log
in the generated debounced function so that you can see that all three components are sharing the same function. That being the case, the function is doing what it is designed to do; it executes once after the elapsed period of time, which is why only the last component is updated.
To get around that behavior you need a unique debounce function for each component. Here are two ways you can do that.
Method One
You can initialize your processData method with what amounts to a placeholder.
methods: {
processData(){}
}
Then, in the created lifecycle event, change the processData method to the debounced method.
created(){
this.processData = debounce(function(){
console.log(this.id)
}, 250)
}
This will give each component a unique debounced function and should take care of the issue where only the last component works properly.
Here is an example modified from the above example.
console.clear()
Vue.component("works",{
props:["value"],
template:`<div>Component #{{_uid}} Value: {{innerValue}}</div>`,
data(){
return {
innerValue: this.value,
}
},
watch:{
value(newVal){
this.processData(newVal)
}
},
methods:{
processData() {}
},
created(){
this.processData = _.debounce(function(newVal){
this.innerValue = newVal
}, 1000)
}
})
new Vue({
el: "#app",
data:{
parentValue: null,
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<script src="https://unpkg.com/[email protected]"></script>
<div id="app">
Type some text. Wait one second. <em>All</em> components are updated.<br>
<input type="text" v-model="parentValue">
<works :value="parentValue"></works>
<works :value="parentValue"></works>
<works :value="parentValue"></works>
</div>
Method Two
Thanks to @RoyJ for suggesting this. You can define the processData
method in data
. Typically you do not do this because you don't often want multiple copies of a function, and that's why the methods
section of a component definition exists, but in a case, like this where you need a unique function for each component, you can define the method in the data function because the data function is called for every instance of the component.
data(){
return {
innerValue: this.value,
processData: _.debounce(function(newVal){
this.innerValue = newVal
}, 1000)
}
},
Here is an example using that approach.
console.clear()
Vue.component("works",{
props:["value"],
template:`<div>Component #{{_uid}} Value: {{innerValue}}</div>`,
data(){
return {
innerValue: this.value,
processData: _.debounce(function(newVal){
this.innerValue = newVal
}, 1000)
}
},
watch:{
value(newVal){
this.processData(newVal)
}
},
})
new Vue({
el: "#app",
data:{
parentValue: null,
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<script src="https://unpkg.com/[email protected]"></script>
<div id="app">
Type some text. Wait one second. <em>All</em> components are updated.<br>
<input type="text" v-model="parentValue">
<works :value="parentValue"></works>
<works :value="parentValue"></works>
<works :value="parentValue"></works>
</div>