Vuejs 2: debounce not working on a watch option

When I debounce this function in VueJs it works fine if I provide the number of milliseconds as a primitive. However, if I provide it as a reference to a prop, it ignores it.

Here's the abbreviated version of the props:

props : {
    debounce : {
        type : Number,
        default : 500
    }
}

Here is the watch option that does NOT work:

watch : {
    term : _.debounce(function () {
        console.log('Debounced term: ' + this.term);
    }, this.debounce)
}

Here is a watch option that DOES work:

watch : {
    term : _.debounce(function () {
        console.log('Debounced term: ' + this.term);
    }, 500)
}

It suspect that it is a scope issue but I don't know how to fix it. If I replace the watch method as follows...:

watch : {
    term : function () {
        console.log(this.debounce);
    }
}

... I get the correct debounce value (500) appearing in the console.

Answers:

Answer

The primary issue here is using this.debounce as the interval when defining your debounced function. At the time _.debounce(...) is run (when the component is being compiled) the function is not yet attached to the Vue, so this is not the Vue and this.debounce will be undefined. That being the case, you will need to define the watch after the component instance has been created. Vue gives you the ability to do that using $watch.

I would recommend you add it in the created lifecycle handler.

created(){
  this.unwatch = this.$watch('term', _.debounce((newVal) => {
     console.log('Debounced term: ' + this.term);
  }, this.debounce))
},
beforeDestroy(){
  this.unwatch()
}

Note above that the code also calls unwatch which before the component is destroyed. This is typically handled for you by Vue, but because the code is adding the watch manually, the code also needs to manage removing the watch. Of course, you will need to add unwatch as a data property.

Here is a working example.

console.clear()

Vue.component("debounce",{
  props : {
    debounce : {
      type : Number,
      default : 500
    }
  },
  template:`
    <input type="text" v-model="term">
  `,
  data(){
    return {
      unwatch: null,
      term: ""
    }
  },
  created(){
    this.unwatch = this.$watch('term', _.debounce((newVal) => {
      console.log('Debounced term: ' + this.term);
    }, this.debounce))
  },
  beforeDestroy(){
    this.unwatch()
  }
})

new Vue({
  el: "#app"
})
<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">
  <debounce :debounce="250"></debounce>
</div>

Answer

Another variation to @Bert's answer is to build the watcher's function in created(),

// SO: Vuejs 2: debounce not working on a watch option

console.clear()

Vue.component("debounce",{
  props : {
    debounce : {
      type : Number,
      default : 500
    }
  },
  template:`
    <div>
      <input type="text" v-model="term">
    </div>
  `,
  data(){
    return {
      term: "",
      debounceFn: null
    }
  },
  created() {
    this.debounceFn = _.debounce( () => {
      console.log('Debounced term: ' + this.term);
    }, this.debounce)
  },
  watch : {
    term : function () {
      this.debounceFn();
    }
  },
})

new Vue({
  el: "#app"
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<div id="app">
  <debounce :debounce="2000"></debounce>
</div>

Example on CodePen

Answer

new Vue({
  el: '#term',
  data: function() {
    return {
      term: 'Term',
      debounce: 1000
    }
  },
  watch: {
    term : _.debounce(function () {
        console.log('Debounced term: ' + this.term);
    }, this.debounce)
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.js"></script>
<div id="term">
  <input v-model="term">
</div>

Answer

The debounced method needs to be abstracted since we need to call the same function everytime the watch is triggered. If we place the debounced method inside a Vue computed or watch property, it will be renewed everytime.

const debouncedGetData = _.debounce(getData, 1000);

function getData(val){
  this.newFoo = val;
}

new Vue({
  el: "#app",
  template: `
    <div>
      <input v-model="foo" placeholder="Type something..." />
      <pre>{{ newFoo }}</pre>
    </div>
`,
    data(){
      return {
        foo: '',
        newFoo: ''
      }
    },
    watch:{
      foo(val, prevVal){
        debouncedGetData.call(this, val);
      }
    }
  
  })
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div id="app"></div>

Good Luck...

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.