How to implement debounce in Vue2?

183,257

Solution 1

I am using debounce NPM package and implemented like this:

<input @input="debounceInput">
methods: {
    debounceInput: debounce(function (e) {
      this.$store.dispatch('updateInput', e.target.value)
    }, config.debouncers.default)
}

Using lodash and the example in the question, the implementation looks like this:

<input v-on:input="debounceInput">
methods: {
  debounceInput: _.debounce(function (e) {
    this.filterKey = e.target.value;
  }, 500)
}

Solution 2

Option 1: Re-usable, no deps

- Recommended if needed more than once in your project

/helpers.js

export function debounce (fn, delay) {
  var timeoutID = null
  return function () {
    clearTimeout(timeoutID)
    var args = arguments
    var that = this
    timeoutID = setTimeout(function () {
      fn.apply(that, args)
    }, delay)
  }
}

/Component.vue

<script>
  import {debounce} from './helpers'

  export default {
    data () {
      return {
        input: '',
        debouncedInput: ''
      }
    },
    watch: {
      input: debounce(function (newVal) {
        this.debouncedInput = newVal
      }, 500)
    }
  }
</script>

Codepen


Option 2: In-component, also no deps

- Recommended if using once or in small project

/Component.vue

<template>
    <input type="text" v-model="input" />
</template>

<script>
  export default {
    data: {
        timeout: null,
        debouncedInput: ''
    },
    computed: {
     input: {
        get() {
          return this.debouncedInput
        },
        set(val) {
          if (this.timeout) clearTimeout(this.timeout)
          this.timeout = setTimeout(() => {
            this.debouncedInput = val
          }, 300)
        }
      }
    }
  }
</script>

Codepen

Solution 3

Assigning debounce in methods can be trouble. So instead of this:

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

You may try:

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

It becomes an issue if you have multiple instances of a component - similar to the way data should be a function that returns an object. Each instance needs its own debounce function if they are supposed to act independently.

Here's an example of the problem:

Vue.component('counter', {
  template: '<div>{{ i }}</div>',
  data: function(){
    return { i: 0 };
  },
  methods: {
    // DON'T DO THIS
    increment: _.debounce(function(){
      this.i += 1;
    }, 1000)
  }
});


new Vue({
  el: '#app',
  mounted () {
    this.$refs.counter1.increment();
    this.$refs.counter2.increment();
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>

<div id="app">
  <div>Both should change from 0 to 1:</div>
  <counter ref="counter1"></counter>
  <counter ref="counter2"></counter>
</div>

Solution 4

Very simple without lodash

handleScroll: function() {
  if (this.timeout) 
    clearTimeout(this.timeout); 

  this.timeout = setTimeout(() => {
    // your action
  }, 200); // delay
}

Solution 5

I had the same problem and here is a solution that works without plugins.

Since <input v-model="xxxx"> is exactly the same as

<input
   v-bind:value="xxxx"
   v-on:input="xxxx = $event.target.value"
>

(source)

I figured I could set a debounce function on the assigning of xxxx in xxxx = $event.target.value

like this

<input
   v-bind:value="xxxx"
   v-on:input="debounceSearch($event.target.value)"
>

methods:

debounceSearch(val){
  if(search_timeout) clearTimeout(search_timeout);
  var that=this;
  search_timeout = setTimeout(function() {
    that.xxxx = val; 
  }, 400);
},
Share:
183,257

Related videos on Youtube

MartinTeeVarga
Author by

MartinTeeVarga

Studied Computer Graphics - Computational Geometry. Currently living and working in Sydney. Java, JavaScript, TDD. Book author and hobby game programmer: Mr. Dandelion's Adventures, and blogging about Android Game Development as well (with occasional game review). Running to keep fit, travelling the world and learning Japanese.

Updated on November 17, 2021

Comments

  • MartinTeeVarga
    MartinTeeVarga over 2 years

    I have a simple input box in a Vue template and I would like to use debounce more or less like this:

    <input type="text" v-model="filterKey" debounce="500">
    

    However the debounce property has been deprecated in Vue 2. The recommendation only says: "use v-on:input + 3rd party debounce function".

    How do you correctly implement it?

    I've tried to implement it using lodash, v-on:input and v-model, but I am wondering if it is possible to do without the extra variable.

    In template:

    <input type="text" v-on:input="debounceInput" v-model="searchInput">
    

    In script:

    data: function () {
      return {
        searchInput: '',
        filterKey: ''
      }
    },
    
    methods: {
      debounceInput: _.debounce(function () {
        this.filterKey = this.searchInput;
      }, 500)
    }
    

    The filterkey is then used later in computed props.

  • MartinTeeVarga
    MartinTeeVarga over 7 years
    Thanks for this. I found a similar example in some other Vue docs: vuejs.org/v2/examples/index.html (the markdown editor)
  • mix3d
    mix3d about 7 years
    Wouldn't this call debounceInput() twice for each change? v-on: will detect the input changes and call debounce, AND because the model is bound, searchInput's watch function will ALSO call debounceInput... right?
  • MartinTeeVarga
    MartinTeeVarga about 7 years
    @mix3d Do not consider this answer. It was just my investigation I did not want to put in the question. You are most likely right. Check the accepted answer. It's correct and I edited it to match the question.
  • mix3d
    mix3d about 7 years
    My mistake... I didn't realize you had answered your own question, ha!
  • Valera
    Valera over 6 years
    Proposed solution has a problem when there are several component instances on the page. Problem is described and solution presented here: forum.vuejs.org/t/issues-with-vuejs-component-and-debounce/7‌​224/…
  • MartinTeeVarga
    MartinTeeVarga about 6 years
    Could you explain why assigning debounce in methods can be trouble?
  • MartinTeeVarga
    MartinTeeVarga about 6 years
    See Example links are prone to link-rot. It's better to explain the problem in the answer - it will make it more valuable for the readers.
  • Admin
    Admin almost 6 years
    Thank you very match, i had a bad time trying to understand why the data displayed on the console was right but not applied on the app ...
  • ness-EE
    ness-EE almost 6 years
    e.currentTarget is overwritten to null this way
  • Mike Sheward
    Mike Sheward over 5 years
    @sm4 because instead of using the same shared debounced instance for your desired function, it recreates it each time, thus killing the use of debounce mainly.
  • Matt Komarnicki
    Matt Komarnicki over 5 years
    Thanks, you helped me a lot with my vue.js component…
  • Flame
    Flame over 5 years
    Thanks I was looking for this answer. Apparently when you make a _.debounce of the same function in copies of a component, only the last created component will be called.
  • DominikAngerer
    DominikAngerer over 5 years
    Would recommend to add a v-model=your_input_variable to the input and in your vue data. So you do not rely on e.target but use Vue so you can access this.your_input_variable instead of e.target.value
  • Suau
    Suau over 5 years
    just add it to your data() then.
  • Neon22
    Neon22 about 5 years
    if your input field also had an @input="update_something" action then call this after that.xxx = val that.update_something();
  • Neon22
    Neon22 about 5 years
    in my methods section I used a slightly different syntax which worked for me: debounceSearch: function(val) { if (this.search_timeout) clearTimeout(this.search_timeout); var that=this; this.search_timeout = setTimeout(function() { that.thread_count = val; that.update_something(); }, 500); },
  • Coreus
    Coreus almost 5 years
    This is ok if you're having one or very few instances where you need to debounce input. However, you'll quickly realize you'll need to move this to a library or similar if the app grows and this functionality is needed elsewhere. Keep your code DRY.
  • Ashtonian
    Ashtonian almost 5 years
    you the real hero
  • Kshitiz
    Kshitiz over 4 years
    Just wasted an hour debugging this!
  • Ben Winding
    Ben Winding over 4 years
    I prefer this option because I probably don't need an npm package for 11 lines of code....
  • Alexander Kludt
    Alexander Kludt over 4 years
    This should be the marked answer, this works really well and takes nearly no space at all. Thanks!
  • Michael Hays
    Michael Hays over 4 years
    As much as I love lodash, this is clearly the best answer for a trailing debounce. Easiest to implement as well as understand.
  • Polosson
    Polosson over 4 years
    For those using ES6, it's important to emphasize the use of the anonymous function here: if you use an arrow function you will not be able to access this within the function.
  • Hybrid web dev
    Hybrid web dev about 4 years
    this is actually wrong. You don't need to declare methods in lifecycle hooks as they are linked to their own instance. Not sure where OP got this idea from, but yeah he's 100% wrong.
  • Barney Szabolcs
    Barney Szabolcs about 4 years
    Probably this one should be the accepted solution, with 100+ vote-ups. The OP asked for a compact solution like this, and it nicely decouples the debounce logic.
  • Barney Szabolcs
    Barney Szabolcs about 4 years
    My vote got locked in. The best solution so far is actually the one with 5 vote-ups: stackoverflow.com/a/50347709/1031191 because it neatly decouples the debounce logic from the container.
  • Barney Szabolcs
    Barney Szabolcs about 4 years
    no. OP asked for a vue html property, as is best for this. See: stackoverflow.com/a/50347709/1031191 (not my answer, that is the cleanest so far, even though it has only 5 vote-ups. It deserves a lot more, I think...)
  • pikilon
    pikilon about 4 years
    also is a good thing to add destroyed() { clearInterval(this.timeout) } in order to not having a timeout after destroyed.
  • nunop
    nunop about 4 years
    Could you please add more information about this solution?
  • jpnadas
    jpnadas about 4 years
    Please elaborate a little bit more. Also, note that this is an old thread with well established answers, so can you clarify how your solution is more appropriate for the problem?
  • PJP
    PJP about 4 years
    It helps more if you supply an explanation why this is the preferred solution and explain how it works. We want to educate, not just provide code.
  • Jordash
    Jordash over 3 years
    Out of all the solutions this is the only one that worked reliably.
  • thiebo
    thiebo about 3 years
    Simple, efficient, great !
  • ness-EE
    ness-EE about 3 years
    @Hybridwebdev I reckon he got it from Linus Borg's answer from the Vue forum, so I would say that this is the correct solution forum.vuejs.org/t/…
  • flourigh
    flourigh about 3 years
    hi, possible add a TypeScript Version o helper?
  • Luis Cabrera Benito
    Luis Cabrera Benito almost 3 years
    Thank you! This way I can access this inside the debounced function
  • SimonHawesome
    SimonHawesome almost 3 years
    is any one else getting a jest error when implementing the first option? [Vue warn]: Error in callback for watcher "input": "TypeError: Cannot read property 'call' of undefined"
  • Bcktr
    Bcktr almost 3 years
    It will be so hard if you play with array, because this way is depends with the static data
  • Tom T
    Tom T over 2 years
    @Polosson Why does it need to be an anonymous function? Seems like an arrow function would let you get the correct this. I've implemented the solution above exactly with an arrow function and without, and I'm running into this issues in both cases. Not sure what's wrong.
  • Just a coder
    Just a coder over 2 years
    i am not sure how to use this when text changes on an input field. Can someone show an example?
  • brad
    brad over 2 years
    @Justacoder you need to add an event listener to the input. Google input addEventListener
  • Gery Ruslandi
    Gery Ruslandi over 2 years
    i just encountered this case. you are 3 years ahead of me haha. thankyou random people