A Guide to Debouncing in JavaScript and React | Create a Custom Hook

Learn what's debouncing and how to create a custom useDebounce hook in React.

Introduction

In our daily surfing of the web, we’ve encountered dynamic web features like auto-complete search boxes (as in Google), drag-and-drop functionality, and smooth scrolling effects. These seamless interactions often rely on techniques like debouncing and throttling to ensure a smooth user experience. These techniques are crucial for handling tasks like API calls, scrolling effects, etc., In this article, we will look into implementing the debounce functionality in JavaScript and later convert it into a reusable hook in React.

What is Debouncing?

Debouncing is a technique used in web development to control the rate at which a particular function or action is triggered in response to an event, typically user input or some form of interaction. It ensures that the function is only executed once after a specified period of inactivity following the event, even if the event itself occurs multiple times in quick succession.

Why Debouncing?

Let’s consider a scenario of a social media application where you need to find your friends’ named John and you need to see the results in real time without pressing any button. So whenever you enter a character in the search bar, an API call will be fired to fetch the friend list. But we don’t want that to happen as the API calls to the server is costly and we need to optimize it for efficient usage of the backend resources. To solve this, debounce comes in handy. We will implement the debounce functionality in such a way that the API call won’t be fired until a certain time has elapsed since the last input. Debouncing can also be used in tasks like handling scroll events or preventing excessive form submissions.

Debouncing in JavaScript

In this section, we will see how to implement the debouncing functionality in JavaScript.

We know the setTimeout function in JavaScript will execute a callback after a certain delay. For Debouncing, we will make use of the setTimeout as it would be a good fit. As we know, setTimeout takes in 2 parameters, the callback function and the delay. It returns a value which is the id of the corresponding setTimeout. As and when the user is typing, we will make sure to clear the timeout function so the callback function won’t be fired until there is no input from the user for the duration of the delay.

const debounce = (cbFn, delay = 500) => {
  let timeoutId;
  return (...args) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => {
      cbFn(...args);
    }, delay);
  };
};

In the above, the debounce function takes in the cbFn as the first parameter and the delay as the second parameter with a default value of 500ms. It returns a function that can be called further in our application. When called, the function will clear any previously set timeout and create a brand-new setTimeout function with the desired delay. When there is no input from the user for the desired delay, the callback function will be executed.

To see the debouncing functionality in action, let’s create a simple input and add the debouncing functionality to it.

Create index.html and add the following content.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Debounce</title>
</head>
<body>
    <input type="text">
    <p>The entered value will appear below when there is no input for 1 second</p>
    <div>Entered Value:</div>
    <script src="script.js"></script>
</body>
</html>

Now add the JavaScript in the script.js file.

const input = document.querySelector("input");
const div = document.querySelector("div");

const debounce = (cbFn, delay = 500) => {
  let timeoutId;
  return (...args) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => {
      cbFn(...args);
    }, delay);
  };
};
const callDebouncedFunction = debounce((args) => {
  div.innerText = `Entered Value: ${args}`;
}, 1000);
input.addEventListener("input", (e) => {
  callDebouncedFunction(e.target.value);
});

Here, we have created a simple input field and a <p> element and a <div> element which shows the entered input value. In the script.js file, we have added an event listener to the input field. We have created a callDebouncedFunction by invoking the debounce function and passing in a callback function that updates the div element's text content with the entered value. The debounce delay is set to 1000 milliseconds (1 second) in this case. So the div will be updated only when there is no input from the user for 1 second.

Check the demo for the above in Codepen.

Implementing Debouncing in React

Now, let’s move on to implementing the debouncing functionality in React. Bootstrap a basic react app using Vite using the below command.

# npm
npm create vite@latest react-debounce -- --template react

# Yarn
yarn create vite react-debounce -- --template react

Once the above step is done, Run the below commands to start the local dev server.

# npm
npm install
npm run dev

# yarn
yarn
yarn dev

Let’s implement the debouncing functionality in react. In theApp.jsx file add the following content.

import { useState } from "react";

export default function App() {
  const [debouncedValue, setDebouncedValue] = useState("");

  const debounce = (cbFn, delay = 250) => {
    let timeoutId;
    return (...args) => {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(() => {
        cbFn(...args);
      }, delay);
    };
  };

  const handleChange = debounce((v) => {
    setDebouncedValue(v);
  }, 1000);

  return (
    <div style={{ display: "flex", gap: "5px", flexDirection: "column" }}>
      <input type="text" onChange={(e) => handleChange(e.target.value)} />
      <p style={{ fontSize: "24px" }}>
        The entered value will appear below when there is no input for 1 second
      </p>
      <p style={{ fontSize: "24px" }}>
        Debounced Value:{" "}
        <span style={{ fontWeight: "bold" }}>{debouncedValue}</span>
      </p>
    </div>
  );
}

Similar to the vanilla implementation, we have created an input field.

We use the useState hook to manage the debouncedValue. This state is displayed as the result after debouncing.

We have defined the debounce function and hanldeChange which calls the debounce function. The handleChange method is attached to the onChange of the input field. The debounce delay is set to 1000ms. This ensures that the state updates only when you've paused for a second, avoiding constant updates.

The Custom Debounce Hook

In React, custom hooks are your superpower for encapsulating and reusing functionality across components. In this section, we will implement the custom useDebounce hook.

Create the useDebounce.jsx file and add the below content.

import { useEffect, useState } from "react";

export default function useDebounce(value, delay = 250) {
  const [debouncedValue, setDebouncedValue] = useState("");

  useEffect(() => {
    const timeoutId = setTimeout(() => setDebouncedValue(value), delay);
    return () => {
      clearTimeout(timeoutId);
    };
  }, [value, delay]);

  return debouncedValue;
}

The custom useDebouncehook takes two parameters: the value we want to debounce (value) and an optional delay time (delay) in milliseconds with a default value of 250ms. Inside the hook, we are making use of useState to manage the state of the debounced value. In the useEffect's body, we set the timer using setTimeout to update the debounced value after the delay. This will run whenever one of the values of the dependency array (value and delay) changes. In the useEffect's cleanup function, we clear the previous timers if the value or delay changes before the timer completes, preventing multiple quick updates. This ensures that the value is only updated once the user has paused their input for the desired delay.

Let’s integrate this hook into the App component.

import { useState } from "react";
import useDebounce from "./useDebounce";

export default function App() {
  const [value, setValue] = useState("");
  const debouncedValue = useDebounce(value, 1000);

  return (
    <div style={{ display: "flex", gap: "5px", flexDirection: "column" }}>
      <input
        type="text"
        value={value}
        onChange={(e) => {
          setValue(e.target.value);
        }}
      />
      <p style={{ fontSize: "24px" }}>
        The entered value will appear below when there is no input for 1 second
      </p>
      <p style={{ fontSize: "24px" }}>
        Debounced Value:{" "}
        <span style={{ fontWeight: "bold" }}>{debouncedValue}</span>
      </p>
    </div>
  );
}

We have defined a state variable called value to manage the input element. We are passing this state to the useDebounce hook and also passing the delay of one second. That’s it, we have implemented the debouncing functionality. To see this in action, check the codesandbox here.

Conclusion

In this article, we have seen the basic definition of debouncing and its implementation in vanilla JS. Also, we have converted that into a reusable react hook which can be reused across the components. This functionality can be applied in many use cases like Real-Time searching, Input validation, Infinite scrolling and any use case where you need to delay an action for a desired time. You can take this approach and integrate it into your applications. Thanks for Reading.