How to implement debounce in Vue2?

vue.jsVuejs2Debouncing

vue.js Problem Overview


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.

vue.js Solutions


Solution 1 - vue.js

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 - vue.js

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 - vue.js

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 - vue.js

Very simple without lodash

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

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

Solution 5 - vue.js

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);
},

Solution 6 - vue.js

If you need a very minimalistic approach to this, I made one (originally forked from vuejs-tips to also support IE) which is available here: https://www.npmjs.com/package/v-debounce

Usage:

<input v-model.lazy="term" v-debounce="delay" placeholder="Search for something" />

Then in your component:

<script>
export default {
  name: 'example',
  data () {
    return {
      delay: 1000,
      term: '',
    }
  },
  watch: {
    term () {
      // Do something with search term after it debounced
      console.log(`Search term changed to ${this.term}`)
    }
  },
  directives: {
    debounce
  }
}
</script>

Solution 7 - vue.js

> Please note that I posted this answer before the accepted answer. It's not > correct. It's just a step forward from the solution in the > question. I have edited the accepted question to show both the author's implementation and the final implementation I had used.


Based on comments and the linked migration document, I've made a few changes to the code:

In template:

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

In script:

watch: {
  searchInput: function () {
    this.debounceInput();
  }
},

And the method that sets the filter key stays the same:

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

This looks like there is one less call (just the v-model, and not the v-on:input).

Solution 8 - vue.js

In case you need to apply a dynamic delay with the lodash's debounce function:

props: {
  delay: String
},

data: () => ({
  search: null
}),

created () {
     this.valueChanged = debounce(function (event) {
      // Here you have access to `this`
      this.makeAPIrequest(event.target.value)
    }.bind(this), this.delay)

},

methods: {
  makeAPIrequest (newVal) {
    // ...
  }
}

And the template:

<template>
  //...

   <input type="text" v-model="search" @input="valueChanged" />

  //...
</template>

NOTE: in the example above I made an example of search input which can call the API with a custom delay which is provided in props

Solution 9 - vue.js

Although pretty much all answers here are already correct, if anyone is in search of a quick solution I have a directive for this. https://www.npmjs.com/package/vue-lazy-input

It applies to @input and v-model, supports custom components and DOM elements, debounce and throttle.

Vue.use(VueLazyInput)
  new Vue({
    el: '#app', 
    data() {
      return {
        val: 42
      }
    },
    methods:{
      onLazyInput(e){
        console.log(e.target.value)
      }
    }
  })

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/lodash/lodash.min.js"></script><!-- dependency -->
<script src="https://unpkg.com/vue-lazy-input@latest"></script> 

<div id="app">
  <input type="range" v-model="val" @input="onLazyInput" v-lazy-input /> {{val}}
</div>

Solution 10 - vue.js

To create debounced methods you can use computeds, that way they won't be shared across multiple instances of your component:

<template>
  <input @input="handleInputDebounced">
<template>

<script>
import debounce from 'lodash.debouce';

export default {
  props: {
    timeout: {
      type: Number,
      default: 200,
    },
  },
  methods: {
    handleInput(event) {
      // input handling logic
    },
  },
  computed: {
    handleInputDebounced() {
      return debounce(this.handleInput, this.timeout);
    },
  },
}
</script>

You can make it work with uncontrolled v-model as well:

<template>
  <input v-model="debouncedModel">
<template>

<script>
import debounce from 'lodash.debouce';

export default {
  props: {
    value: String,
    timeout: {
      type: Number,
      default: 200,
    },
  },
  methods: {
    updateValue(value) {
      this.$emit('input', value);
    },
  },
  computed: {
    updateValueDebounced() {
      return debounce(this.updateValue, this.timeout);
    },
    debouncedModel: {
      get() { return this.value; },
      set(value) { this.updateValueDebounced(value); }
    },
  },
}
</script>

Solution 11 - vue.js

If you are using Vue you can also use v.model.lazy instead of debounce but remember v.model.lazy will not always work as Vue limits it for custom components.

For custom components you should use :value along with @change.native

<b-input :value="data" @change.native="data = $event.target.value" ></b-input>

Solution 12 - vue.js

1 Short version using arrow function, with default delay value

file: debounce.js in ex: ( import debounce from '../../utils/debounce' )

export default function (callback, delay=300) {
    let timeout = null
    return (...args) => {
        clearTimeout(timeout)
        const context = this
        timeout = setTimeout(() => callback.apply(context, args), delay)
    }
}
2 Mixin option

file: debounceMixin.js

export default {
  methods: {
    debounce(func, delay=300) {
      let debounceTimer;
      return function() {
       // console.log("debouncing call..");
        const context = this;
        const args = arguments;
        clearTimeout(debounceTimer);
        debounceTimer = setTimeout(() => func.apply(context, args), delay);
        // console.log("..done");
      };
    }
  }
};

Use in vueComponent:

<script>
  import debounceMixin from "../mixins/debounceMixin";
  export default {
   mixins: [debounceMixin],
    	data() {
    		return {
    			isUserIdValid: false,
    		};
    	},
    	mounted() {
    	this.isUserIdValid = this.debounce(this.checkUserIdValid, 1000);
    	},
    methods: {
    	isUserIdValid(id){
    	// logic
    	}
  }
</script>
another option, example

Vue search input debounce

Solution 13 - vue.js

I was able to use debounce with very little implementation.

I am using Vue 2.6.14 with boostrap-vue:

Add this pkg to your package.json: https://www.npmjs.com/package/debounce

Add this to main.js:

import { debounce } from "debounce";
Vue.use(debounce);

In my component I have this input:

          <b-form-input
            debounce="600"
            @update="search()"
            trim
            id="username"
            v-model="form.userName"
            type="text"
            placeholder="Enter username"
            required
          >
          </b-form-input>

All it does is call the search() method and the search method uses the form.userName for perform the search.

Solution 14 - vue.js

If you could move the execution of the debounce function into some class method you could use a decorator from the utils-decorators lib (npm install --save utils-decorators):

import {debounce} from 'utils-decorators';

class SomeService {

  @debounce(500)
  getData(params) {
  }
}

Solution 15 - vue.js

 public debChannel = debounce((key) => this.remoteMethodChannelName(key), 200)

vue-property-decorator

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMartinTeeVargaView Question on Stackoverflow
Solution 1 - vue.jsPrimoz RomeView Answer on Stackoverflow
Solution 2 - vue.jsdigoutView Answer on Stackoverflow
Solution 3 - vue.jsbendytreeView Answer on Stackoverflow
Solution 4 - vue.jspshxView Answer on Stackoverflow
Solution 5 - vue.jsstallingOneView Answer on Stackoverflow
Solution 6 - vue.jsCoreusView Answer on Stackoverflow
Solution 7 - vue.jsMartinTeeVargaView Answer on Stackoverflow
Solution 8 - vue.jsRolandView Answer on Stackoverflow
Solution 9 - vue.jsundefinederrorView Answer on Stackoverflow
Solution 10 - vue.jsCyberAPView Answer on Stackoverflow
Solution 11 - vue.jsAmir KhademView Answer on Stackoverflow
Solution 12 - vue.jsdevzomView Answer on Stackoverflow
Solution 13 - vue.jsFritzView Answer on Stackoverflow
Solution 14 - vue.jsvlio20View Answer on Stackoverflow
Solution 15 - vue.jsMayxxpView Answer on Stackoverflow