DEV Community

Rafal
Rafal

Posted on

How to write a debounce function

What is a debounce function?

debounce in dev tools

When you type "debouce function" into google search box you may get a result similar to the below.

debounce google search

or

"A debounce function is a programming technique used to prevent a function from being called too many times in a short amount of time. It is commonly used in event-driven programming, such as in web development or embedded systems."

In simple terms the above means that a debounce function is designed to ensure that a function is only executed once after a series of rapid events. This is achieved by adding a delay between the time an event occurs and the time the function is executed. If another event occurs during this delay, the timer is reset, and the delay starts again.

The debounce function can be used in various scenarios, such as handling user input events like button clicks, key presses, or mouse movements, or in scenarios where an API request needs to be made but should not be made too frequently. By using a debounce function, it helps to ensure that the program only performs the desired action once, improving the efficiency and stability of the code.

Real life analogy

Imagine you're in a room with a light switch that controls a lamp. You want to turn the light on by flipping the switch, but you notice that the switch is a little bit loose and wobbly.

If you quickly flip the switch on and off multiple times, the lamp might flicker and turn off unexpectedly. This is because the switch is bouncing back and forth and not staying in one position long enough for the lamp to register the change.

To prevent this from happening, you could use a debounce function. You could place a small delay between each time you flip the switch, so that the switch has time to settle in one position before you flip it again. This would ensure that the lamp only turns on or off once, regardless of how many times you flip the switch.

In other words, the debounce function adds a delay between the input (flipping the switch) and the output (turning the lamp on or off), to prevent rapid, unintended changes. It ensures that the output reflects the true intention of the input, and prevents unwanted flickering or bouncing.

Let's start simple

Let's write a simple debounce function.

function debounce(func, delay) {
  let timeoutId;
  return function() {
    const context = this;
    const args = arguments;
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => {
      func.apply(context, args);
    }, delay);
  };
}

// Example usage
const myButton = document.querySelector('#my-button');
myButton.addEventListener('click', debounce(handleButtonClick, 1000));

function handleButtonClick() {
  console.log('Button clicked!');
}
Enter fullscreen mode Exit fullscreen mode

In this example, we define a debounce function that takes in a function (func) and a delay time (delay) as arguments. It returns a new function that wraps around the original function.

When the wrapped function is called (in this case, when the button is clicked), it clears any previously set timeout using clearTimeout(), and sets a new timeout using setTimeout(). The setTimeout() function delays the execution of the original function (func) by the specified delay time (delay). If the wrapped function is called again before the timeout has elapsed, the previous timeout is cleared and a new one is set.

By using the debounce function to wrap around the handleButtonClick function, we ensure that the handleButtonClick function is only called once within a 1000ms (1 second) window, even if the button is clicked multiple times during that time period. This helps to prevent unintended behavior and improves the performance of our code.

Let's add some typescript

Let's typescriptify the above example.

function debounce<T extends Function>(func: T, delay: number): (...args: any[]) => void {
  let timeoutId: ReturnType<typeof setTimeout>;
  return function(this: any, ...args: any[]) {
    const context = this;
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => {
      func.apply(context, args);
    }, delay);
  };
}

// Example usage
const myButton = document.querySelector('#my-button');
myButton?.addEventListener('click', debounce(handleButtonClick, 1000));

function handleButtonClick(this: HTMLElement, event: MouseEvent) {
  console.log('Button clicked!', this, event);
}

Enter fullscreen mode Exit fullscreen mode

In this TypeScript version of the code, we added type annotations to the debounce function's parameters and variables.

We used a generic type T that extends Function to specify the type of the func parameter. This allows us to capture the type of the original function and ensure that the wrapped function has the same type signature.

We also added a return type of (...args: any[]) => void to indicate that the debounce function returns a new function that takes any number of arguments and does not return a value.

In the return function() statement, we added a this parameter with type any to ensure that the function has access to the original context. We also added a ...args: any[] parameter to capture any arguments passed to the function, which allows us to pass them to the func function later.

In the handleButtonClick function, we added type annotations for the this parameter and the event parameter to ensure that TypeScript knows the types of these values. We used HTMLElement as the type for the this parameter since myButton is a HTMLElement. We used MouseEvent as the type for the event parameter since it is an event of type MouseEvent.

What about React?

Now that we know how to write a debounce function in javascript and in typescript, let's try to apply our knowledge and write a custom react hook.

import { useState, useEffect } from 'react';

function useDebounce<T extends Function>(func: T, delay: number): T {
  const [debouncedFunc, setDebouncedFunc] = useState<T>(func);

  useEffect(() => {
    const timeoutId = setTimeout(() => {
      setDebouncedFunc(func);
    }, delay);

    return () => clearTimeout(timeoutId);
  }, [func, delay]);

  return debouncedFunc;
}

// Example usage
function MyComponent() {
  const [count, setCount] = useState(0);

  const handleClick = useDebounce(() => {
    console.log('Button clicked!', count);
  }, 1000);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

In this example, we created a new useDebounce hook that takes in a function (func) and a delay time (delay) as arguments. It returns a debounced function of the same type as the original function.

The hook uses the useState hook to store the debounced function, and the useEffect hook to set a timeout that updates the debounced function after the specified delay. The useEffect hook also clears the timeout when the component unmounts or when the func or delay dependencies change.

To use the hook in a component, we can call it inside a function component and pass the original function as an argument. In the example above, we used the useDebounce hook to create a debounced version of the handleClick function that is called when the button is clicked.

Note that in this example, we are not passing any context or arguments to the debounced function. If you need to pass context or arguments, you can modify the hook implementation to handle this.

Top comments (0)