17

my view:

ns-input#filterName(type="text", v-model="filterName", @keyup="searchTimeOut()")

in my vue code:

getUsers() {
   .
   .
   .
   API.users.index(params).then(blabla);
   .
   .
   .
},

searchTimeOut() {
  let timeout = null;
  clearTimeout(timeout);
  // Make a new timeout set to go off in 800ms
  timeout = setTimeout(() => {
    this.getUsers();
    console.log("hi")
  }, 800);
},

I want call getUsers() only once after i stop typing and 800 ms. Right now, i'm calling getUsers() every time i write a letter.

oniondomes
  • 2,034
  • 13
  • 22
tomatito
  • 391
  • 1
  • 5
  • 16
  • 4
    Take a look at [`.lazy` modifier](https://vuejs.org/v2/guide/forms.html#lazy) or [How to implement debounce in Vue2?](https://stackoverflow.com/q/42199956/1218980) – Emile Bergeron Apr 07 '18 at 20:05
  • @EmileBergeron how would `.lazy` help? – oniondomes Apr 07 '18 at 20:10
  • 1
    `v-model.lazy` will trigger the change only on `change` events, which are triggered when the input loses focus. Maybe it doesn't work in your specific case, but that may be of used when you want to delay the `v-model` sync. – Emile Bergeron Apr 07 '18 at 20:16
  • Well, it's not quite relevant to the question. This isn't really a delay. – oniondomes Apr 07 '18 at 20:20
  • @oniondomes At first glance, it wasn't clear that this was a search suggestion input, that's why I linked to both `lazy` and the debounce question. But still, `.lazy` is a common solution for when you want to "delay" an API call to after the user has finished typing. – Emile Bergeron Apr 07 '18 at 20:23
  • @EmileBergeron i see your point, although I would argue that this is a good and intuitive solution. even assuming this is a common one. – oniondomes Apr 07 '18 at 20:34

2 Answers2

51

You drop this.timer value before clearing the interval. Do this instead:

searchTimeOut() {  
    if (this.timer) {
        clearTimeout(this.timer);
        this.timer = null;
    }
    this.timer = setTimeout(() => {
        // your code
    }, 800);
}
oniondomes
  • 2,034
  • 13
  • 22
  • @tomatito there are plenty of universal solutions for this problem. You can take a look at this thread https://stackoverflow.com/a/1909508/9339801 – oniondomes Apr 07 '18 at 20:16
  • I think you need to make a handler for keydown as well. What will happen if I type 1 character and then I press other key with a long time or without even releasing it? – Richie Permana Nov 13 '20 at 04:42
3

There is a better solution!

Debouncing is a technique to frequently restrict the calling of a time-consuming function, by delaying the execution of the function until a specified time to avoid unnecessary CPU cycles, and API calls and improve performance.

You can visit this site for a visual representation of this technique in JS

To implement debounce:

  1. In helper.js from utilities directory export debounce function
// utilities/helper.js
export const debounce = (func, delay = 600, immediate = false) => {
  let timeout
  return function () {
    const context = this
    const args = arguments
    const later = function () {
      timeout = null
      if (!immediate) func.apply(context, args)
    }
    const callNow = immediate && !timeout
    clearTimeout(timeout)
    timeout = setTimeout(later, delay)
    if (callNow) func.apply(context, args)
  }
}
  1. In your component you must import debounce function and assign it to the variable. I will do the assignment in mounted().
<script>
import { debounce } from '~/utilities/helper'

export default {
  name: 'MyComponent',
  data() {
    return {
      debounceFn: null,
      filterName: ''
    }
  },
  mounted() {
    this.debounceFn = debounce(() => this.getUsers(), 800)
  },
  methods: {
    onKeyup() {
      this.debounceFn()
    },
    getUsers() {
      // Logic
    }
  }
}
</script>
  1. Now connect script to the DOM
<template>
  <input type="text" v-model="filterName" @keyup="onKeyup" />
</template>

As a result, by doing the above steps, getUsers() only call once after you stop typing with an 800ms delay.