Skip to main content

Using JavaScript Debounce to Only Send Network Request When User Stops Typing

When we need to subscribe to a keyup event from users on a search input and then fire network request to our server using fetch() api, these are codes to achieve that.

index.js
const searchProducts = async (e) => {
  const name = e.target.value
  const res = await fetch(`https://someapi.com/products?name=${name}`)
  const users = await res.json()
}

const input = document.querySelector('input.search')
input.addEventListener('keyup', searchProducts)

But by doing this, we will be sending fetch() request every time a user is typing a letter in the search input, which is not ideal. We can refactor the codes to only send search request when user has finished typing.

index.js
const searchProducts = async (e) => {
  const name = e.target.value
  const res = await fetch(`https://someapi.com/products?name=${name}`)
  const products = await res.json()
}

let timeout // 1
const debounce = (e) => {
  clearTimeout(timeout) // 3
  timeout = setTimeout(() => { // 2
    searchProducts(e.target.value)
  }, 1000)
}

const input = document.querySelector('input.search')
input.addEventListener('keyup', debounce)

If you’re a junior developer like me, this might looks a bit confusing. What’s going here is that we’re setting a global variable timeout (1), and then assign a setTimeout to it (2), but we need to clear that timeout otherwise searchProducts() will still get fired if user hasn’t finished typing, meaning that 1s has passed since user has started typing, but that doesn’t necessarily mean user has finished typing.

Another way we can look at it is setTimeout() will return a timeoutID.

index.js
/** 
 * assume we type our first letter in search box,
 * now debounce() will run once
 * we've set our first timeout and its timeoutID
 * will be returned and stored on timeout variable
 * timeoutID = 1 at this moment
 */
let timeout 
const debounce = (e) => {
  timeout = setTimeout(() => { // timeoutID = 1
    searchProducts(e.target.value)
  }, 1000)
}

/** 
 * now we type our second letter in search box,
 * debounce() will run again
 * we've set our second timeout and its timeoutID
 * will be returned and stored on timeout variable
 * timeoutID = 2 at this moment
 */
let timeout 
const debounce = (e) => {
  timeout = setTimeout(() => { // timeoutID = 2
    searchProducts(e.target.value)
  }, 1000)
}

// but our first timeout is still valid,
// if we don't cancel that, it will still run
// when 1s has passed since it's been created
// that is why we need to clear the timeout before 
// we set the next timeout
let timeout 
const debounce = (e) => {
  clearTimeout(timeout)
  timeout = setTimeout(() => { // timeoutID = 3
    searchProducts(e.target.value)
  }, 1000)
}