A Guide to Debouncing in JavaScript and React | Create a Custom Hook
Tags: webdev, react, reacthooks, javascript
Published: 2023-11-15 17:27:48

## 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.

```jsx
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 which 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.

```html
<!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.

```jsx
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](https://codepen.io/knirmalkumar/pen/YzBwbdG). 

## 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.

```bash
# 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.

```bash
# npm
npm install
npm run dev

# yarn
yarn
yarn dev
```

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

```jsx
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 a `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. 

```jsx
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 `useDebounce`hook 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. 

```jsx
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](https://codesandbox.io/s/react-debounce-ssgyk8) 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.