Issues with VueJS component and debounce

Greetings fellow devs,

I’m working on a small in-house project to display data in a list/table.
I currently have a component build which accepts a ‘api-url’ where it can get it’s data and a filter prop to accept a key value list of filters. These filters will get appended to the url ( example.com/users?filter=something&search=testing ).

To make sure i’m not calling the api too often I’ve implemented a debounce (using lodash) on the filters through a watch.

watch: {                                                                                                                                                                                              
    filters: {                                                                                                                                                                                        
        handler: _.debounce(function() {                                                                                                                                                              
            this.resetCache();                                                                                                                                                                        
            this.getData(1);                                                                                                                                                                          
        }, 300),                                                                                                                                                                                      
        deep: true                                                                                                                                                                                    
    }                                                                                                                                                                                                 
},

The problem I’m running into is when I’ve got 2 instances of the list on a single page, when updating the filters only the last initiated component will actually run debounced function.
When applying the same search filter to both instances makes this extra obvious, which is what i’m trying to accomplish.

Aside from removing the debounce entirely from the component and putting it in front of the component I’ve been unable to find a reasonable solution that works.

Any assistance trying to get around or hopefully fix this would be much appreciated.

JSFiddle showing what happens

Can you please provide a reproduction of the issue on jsfiddle or a similar service?

Added a JS fiddle with the reproduction

2 Likes

I modified your watch, instead of using the deep watch handle, I used the string expression to watch and changed to use “self” and now seems to be working:

  watch:
  {                                                        
    "filters.search":function()
    {
    	var self=this;
	_.debounce(function(){self.finalText = self.filters.search;}, 300)();
    }
   }

@nelfer While testing this I’m realizing this breaks the intended functionality.
I’m referencing filters in general because i need this to trigger on any update to the filters object and not just the search property that might not be there.

Secondly by calling the _.debounce directly as a function it defeats the purpose of the debounce.
By changing filters.search the ‘debounce’ function gets called for each letter in the filters.search property.
For example by typing ‘test’ you would call the debounce 4 times instead of the 1 time you want with debounce.

Thank you for thinking with me though.

Ah! Ok. I’m not familiar with the “deep” watch of objects. So I’ll have to read more about it. So far in everything I’ve done it’s only a matter of simple properties.
It could be a bug in how the watchers trigger, because both are pointing to the same object.
And yes I see your point about the debounce. Tried different variations with binding to the object but still no go. I had used debounce before (not this implementation) and what we did was just a method of vue that returned the debounced function. But in this kind of “watch” is not resolving “this” to point to the instance of the component.

This version seems to work:

https://jsfiddle.net/k1b0z55c/

Created a method (empty). In the hook event “created” I assigned the debounced function I wanted.

In watch handler, I call that method. It works for multiple and it debounces to only 1 call every 300 ms

This gets rid of the empty function:

https://jsfiddle.net/k1b0z55c/1/

Still I’m kind of lost why the original doesn’t really work as indended.

3 Likes

I’m thinking, based on the comment “Aside from removing the debounce”, which makes it work, maybe it’s an issue with lodash combined with context of “this” when the watchers are being assigned.
Because I know that doing something like

handler: this.instanceMethod

Doesn’t work.

Thank you @nelfer and @LinusBorg,

Your versions do indeed work and i’ll use this.
It’s still weird why it doesn’t the way you think it should.
But unfortunately my knowledge of javascript isn’t good enough to figure out why/why not.

Thanks immensely for your help.

For another example of this problem, trying to parse some input on multiple inputs:

If you quickly tab through the inputs it breaks debounce:
Not working

Setting the debouncer in created() fixes the issue:
Working

1 Like

Any insights why original implementation doesn’t work?
Is problem the same as why “data” must be a function that returns object?

Looks like there is another way to solve this problem - debounced function can be made as computed function.

Here’s an example of the problem that doesn’t require interaction:
https://jsfiddle.net/8edwwx9j/7/

With multiple components, the debounce only fires on one.

Seems like the docs (and here) should suggest assigning debounce in create, not in methods.

// Bad
methods: {
  foo: _.debounce(function(){}, 1000)
}

// Good
create () {
  this.foo = _.debounce(function(){}, 1000);
}
1 Like

Open an issue on github :wink:

I ran into this issue with code that watches for changes in the viewport width to repaint some d3 charts after a debounce delay.

When I define my debounce function in the watch, the function only fires in one component instance. Example: https://codesandbox.io/s/713y67rv6

When the code is changed so that the watch calls a debounce function defined in the created event (thanks to the tip from @bendytree) , the debounce function is called for all component instances. Example: https://codesandbox.io/s/w7lqpyxn88

As suggested by @LinusBorg, I opened an issue in github: https://github.com/vuejs/vuejs.org/issues/1581

1 Like

I think that thisshould be available when declaring methods, otherwise the behavior gets inconsistent.

Thanks for the ‘workaround’!

Vue reuses component’s methods property on all of its instances. Normally, when vue invokes the method, it binds this to the method context. When the method is invoked, it sees the instance specific data property and computed property.

Now in the case of using _.debounce, when it is invoked multiple times in different components, it is the same “debounced method” being invoked. debounce internally has no idea that this context changed, so it thinks it needs to debounce this method call.

The documentation provides a solution, namely create an instance-specific debounced method so when the debounced method gets triggered in different components, they are functioning independently and specific to the component instance.

https://vuejs.org/v2/guide/computed.html#Watchers