Exambodh - Practice Aptitude, Reasoning & GK Questions
UPlay Quiz
Interview Questions

How to Build a Debounced Search Input in React.js

By Exambodh Team2 Jun 20265 min read24 Views

What is Debouncing?

Debouncing is a performance optimization technique. When a user types quickly in a search input, making an API call on every key press is the wrong approach. By using debounce, we only call the API after the user has stopped typing.

"Do not call the API on every key press — call it only after the user stops typing."

Why Debounced Search is Needed?

Suppose a user types:

r
re
rea
reac
react

If we make an API call on every character, 5 API calls will be triggered. But by using debounce, only one API call is made on the final search keyword.

React Debounced Search Flow

1. User Types

The input state gets updated.

2. Timer Starts

A setTimeout delay begins.

3. Clear Old Timer

If the user types again, the old timer is cleared.

4. API Call

The API is called only after the user stops typing.

Example 1: Debounced Search Input

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

export default function DebouncedSearch() {
  const [searchTerm, setSearchTerm] = useState("");
  const [debouncedValue, setDebouncedValue] = useState("");
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(searchTerm);
    }, 500);

    return () => {
      clearTimeout(timer);
    };
  }, [searchTerm]);

  useEffect(() => {
    if (!debouncedValue.trim()) {
      setResults([]);
      return;
    }

    const fetchData = async () => {
      try {
        setLoading(true);

        const response = await fetch(
          `https://jsonplaceholder.typicode.com/users?name_like=${debouncedValue}`
        );

        const data = await response.json();
        setResults(data);
      } catch (error) {
        console.error("Search API Error:", error);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [debouncedValue]);

  return (
    <div className="mx-auto max-w-xl rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
      <h2 className="mb-4 text-2xl font-bold text-gray-900">
        Debounced Search Input
      </h2>

      <input
        type="text"
        placeholder="Search users..."
        value={searchTerm} => setSearchTerm(e.target.value)}
        className="w-full rounded-lg border border-gray-300 px-4 py-3 text-gray-900 outline-none focus:border-blue-500"
      />

      {loading && <p className="mt-4 text-sm text-gray-600">Searching...</p>}

      <div className="mt-5 space-y-3">
        {results.map((user) => (
          <div key={user.id} className="rounded-lg border border-gray-200 p-4">
            <h3 className="font-semibold text-gray-900">{user.name}</h3>
            <p className="text-sm text-gray-600">{user.email}</p>
          </div>
        ))}
      </div>
    </div>
  );
}

Code Explanation

1. searchTerm State

This state stores the value typed by the user.

2. debouncedValue State

This is the final value that gets updated only after the delay has completed.

3. clearTimeout

If the user types again, the old timer gets cancelled.

Example 2: Custom useDebounce Hook

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

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

  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(timer);
    };
  }, [value, delay]);

  return debouncedValue;
}

export default function SearchWithCustomHook() {
  const [query, setQuery] = useState("");
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(false);

  const debouncedQuery = useDebounce(query, 600);

  useEffect(() => {
    if (!debouncedQuery.trim()) {
      setUsers([]);
      return;
    }

    async function searchUsers() {
      try {
        setLoading(true);

        const res = await fetch(
          `https://jsonplaceholder.typicode.com/users?name_like=${debouncedQuery}`
        );

        const data = await res.json();
        setUsers(data);
      } catch (error) {
        console.log("Error:", error);
      } finally {
        setLoading(false);
      }
    }

    searchUsers();
  }, [debouncedQuery]);

  return (
    <div className="mx-auto max-w-xl p-6">
      <input
        value={query} => setQuery(e.target.value)}
        placeholder="Type to search..."
        className="w-full rounded-lg border border-gray-300 px-4 py-3"
      />

      {loading && <p className="mt-3">Loading...</p>}

      {users.map((user) => (
        <div key={user.id} className="mt-3 rounded-lg border p-3">
          <strong>{user.name}</strong>
          <p>{user.email}</p>
        </div>
      ))}
    </div>
  );
}

Best Practices

  • Keep the debounce delay between 300ms and 700ms.
  • Avoid making an API call when the search query is empty.
  • Always show a loading state.
  • Always add proper error handling.
  • In large applications, creating a custom hook is the better approach.

Interview Answer

In a Debounced Search Input, we optimize API calls by using setTimeout to delay the request. Instead of calling the API on every keystroke, we wait for the user to stop typing. If the user types again within the delay period, clearTimeout cancels the old timer and a new one starts. This reduces unnecessary API calls, improves performance, and reduces server load.

Share this article: