Prasetya Priyadi
Resources

Built with Next.js and Chakra UI, deployed with Vercel. All text is set in the Inter typeface.

Copyright 2025 | prasetya_webspace-v2.0.8

Use debounce function javascript in your React

Use debounce function javascript in your React

Last update on Wednesday, 26 February 2025, 14:46:43 WIB
Technical DiscussionEnglishTypescriptReact


Contributor

PP

Prasetya Ikra Priyadi

[email protected]

In some cases, I need to ensure users receive the right content based on their queries. This is typically done by allowing them to fetch data via API calls. However, when users rapidly change their queries, it can lead to multiple API requests in a short time, which may slow down performance and increase server load.

To maintain a smooth user experience while keeping API calls efficient, we can use a debounce function. This technique delays the execution of a function until the user stops typing or interacting for a specified time. By implementing debouncing in React, we can prevent unnecessary API calls, reduce re-renders, and optimize overall app performance.

What is debounce?

The term debounce comes from electronics, where it is used to prevent unintended multiple signals caused by mechanical button presses. When you press a button, such as on a TV remote, the physical contact inside the switch doesn’t settle instantly. Instead, it rapidly oscillates (or "bounces") for a few milliseconds before stabilizing, causing the microchip to register multiple clicks instead of one.

To prevent this, debouncing introduces a short delay after the first signal is detected. During this delay, the microchip ignores additional signals from the button, ensuring that only a single input is processed, even if the button physically bounces. This same concept is applied in software to control the frequency of function execution, such as preventing excessive API calls in React applications.

Debounce Function

To implement a debounce function in javascript, we can define a function like this

export function debounce<T extends (...args: never[]) => void>(
  callback: T,
  timeout = 300
): (...args: Parameters<T>) => void {
  let timer: ReturnType<typeof setTimeout>;

  return (...args: Parameters<T>) => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      callback(...args);
    }, timeout);
  };
}

The provided debounce function takes two parameters: a callback function and an optional timeout (in my cases, the default is 300 miliseconds, but its up to you). Inside the function, a variable timer is declared to store a reference to the setTimeout function. When the returned function is called, it first clears any existing timeout using clearTimeout(timer), ensuring that any previously scheduled execution of callback is canceled. Then, a new timeout is set using setTimeout, which will execute the callback function after the specified timeout period. This process effectively resets the timer each time the function is called, preventing the callback from executing too frequently. Only when no further calls occur within the timeout duration does the function finally execute. This approach is useful in scenarios such as handling user input, where it ensures that expensive operations (e.g., API calls) are only triggered after the user has finished typing, improving performance and efficiency.

When you need a Debounce Function

We know that debounce helps prevent multiple events from being triggered repeatedly in a short period. This is especially useful when a user needs to call an API with a changing query. I often encounter this scenario when implementing an auto-complete search, where the user types, and a list of matching data is displayed dynamically

https://prasetyapriyadi.my.id/resources
https://prasetyapriyadi.my.id/resources

This example allow user to search an article based on title and description that user needs. They simply type the keyword on search input box and the app will automatically calling the API to get the list of Article based on those query.

import React, { useState, useEffect } from "react";

interface Article {
  id: number;
  title: string;
}

export default function ArticleList() {
  const [articles, setArticles] = useState<Article[]>([]);
  const [search, setSearch] = useState<string>("");
  const [loading, setLoading] = useState<boolean>(false);

  async function fetchArticles(query: string = ""): Promise<void> {
    setLoading(true);
    try {
      const res = await fetch(`/api/articles?search=${query}`);
      const data: Article[] = await res.json();
      setArticles(data);
    } catch (error) {
      console.error("Error fetching articles:", error);
    } finally {
      setLoading(false);
    }
  }

  useEffect(() => {
    fetchArticles(search);
  }, [search]);

  return (
    <div>
      <input
        type="text"
        placeholder="Search articles..."
        value={search}
        onChange={(e) => setSearch(e.target.value)}
      />
      {loading && <p>Loading...</p>}
      <ul>
        {articles.map((article) => (
          <li key={article.id}>{article.title}</li>
        ))}
      </ul>
    </div>
  );
}

With this code, the search feature functions as expected. When a user types in the search input box, it updates the search state, which in turn triggers the useEffect hook to call fetchArticles, retrieving a new list of articles based on the updated search query.

However, there is an inefficiency in how the API is being called. Every single character input triggers a state change, which immediately triggers a new API request. For example, if a user types "hello", the API is called five times with queries: "h", "he", "hel", "hell", and finally "hello". This results in unnecessary API calls, increasing server load and potentially slowing down the application.

Ideally, we want to delay the API request until the user has finished typing their search query. This ensures that we only fetch relevant results based on the completed input rather than sending redundant requests. This is where the debounce function comes to the rescue.

import React, { useState, useEffect } from "react";

interface Article {
  id: number;
  title: string;
}

export default function ArticleList() {
  const [articles, setArticles] = useState<Article[]>([]);
  const [search, setSearch] = useState<string>("");
  const [loading, setLoading] = useState<boolean>(false);

  async function fetchArticles(query: string = ""): Promise<void> {
    setLoading(true);
    try {
      const res = await fetch(`/api/articles?search=${query}`);
      const data: Article[] = await res.json();
      setArticles(data);
    } catch (error) {
      console.error("Error fetching articles:", error);
    } finally {
      setLoading(false);
    }
  }

  // Debounced version of fetchArticles
  const debouncedFetchArticles = useCallback(debounce(fetchArticles, 800), []);

  useEffect(() => {
    debouncedFetchArticles(search);
  }, [search, debouncedFetchArticles]);

  return (
    <div>
      <input
        type="text"
        placeholder="Search articles..."
        value={search}
        onChange={(e) => setSearch(e.target.value)}
      />
      {loading && <p>Loading...</p>}
      <ul>
        {articles.map((article) => (
          <li key={article.id}>{article.title}</li>
        ))}
      </ul>
    </div>
  );
}

With this change in our code, we pass the fetchArticles function as a callback to the debounce function. Here’s what happens:

When the user starts typing in the search input, the debounce function schedules the callback (fetchArticles) using setTimeout, waiting 800 milliseconds before executing it. This ensures the user has at least 800 milliseconds to finish typing before the API call is triggered.

If the user continues typing within this 800-millisecond window, the debounce function cancels the previously scheduled execution and resets the timer with the updated query. This cycle repeats until the user stops typing for at least 800 milliseconds.

Once the delay has passed without any further input, the callback (fetchArticles) finally executes with the most recent query, ensuring that only the final input is used for the API request. This approach prevents unnecessary API calls and optimizes performance. You can try this solution on my Resources Page to explore it in more detail.

Wrapping up

Now you understand how and why to use the debounce function in react—simple and effective, right?

With debouncing, when we type a search query, the results will only update after given time we defined once we stop typing. This prevents unnecessary API calls and improves performance. Debouncing has many useful applications. It helps reduce excessive API requests, ensuring we don’t overload our server

Article For You

Loading...