Back to all notes

Debounce — Wait for the User to Finish Typing

Delay expensive work (like search) until input settles using a simple debounced hook.

2 min read

Introduction

Debouncing delays running a function until input has “settled” for a short period. It’s a simple way to avoid firing expensive work (like network requests) on every keystroke.

Why this matters

Running a fetch on every keystroke wastes work and can make the UI feel sluggish. Debouncing waits for a pause before running the action.

The problem

Without debouncing, you trigger side effects on every keypress, flooding your app and network.

Inefficient approach

Calling the effect on each input change spams logs/requests:

import { useState, useEffect } from "react";

function App() {
  const [searchTerm, setSearchTerm] = useState("");

  useEffect(() => {
    // Imagine this is a fetch; it runs on every keystroke
    console.log("Searching for:", searchTerm);
  }, [searchTerm]);

  return (
    <div>
      <h1>Debounce</h1>
      <input
        type="text"
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
        placeholder="Search..."
      />
      <p>Searching for: {searchTerm}</p>
    </div>
  );
}

export default App;

The solution

Create a useDebounce hook that returns a stabilized value after a delay.

import { useState, useEffect } from "react";

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

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

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

  return debouncedValue;
}

function App() {
  const [searchTerm, setSearchTerm] = useState("");
  const debouncedSearchTerm = useDebounce(searchTerm, 500);

  useEffect(() => {
    if (debouncedSearchTerm) {
      console.log("Searching for:", debouncedSearchTerm);
    }
  }, [debouncedSearchTerm]);

  return (
    <div>
      <h1>Debounce</h1>
      <input
        type="text"
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
        placeholder="Search..."
      />
      <p>Search term: {searchTerm}</p>
      <p>Debounced: {debouncedSearchTerm}</p>
    </div>
  );
}

export default App;

Step-by-step

  1. Inside useDebounce, create debouncedValue state and initialize with value.

  2. In an effect, set a timeout to update debouncedValue after delay.

  3. Clean up the timeout when value or delay changes.

  4. Use the debounced value when making requests or expensive computations.

Tips

  • Always clean up the timeout to prevent memory leaks.

  • Consider leading vs trailing behavior depending on UX needs.

  • For requests, cancel in‑flight fetches when the debounced value changes.

Knowledge base

Problem snippet:

useEffect(() => console.log("Searching:", searchTerm), [searchTerm]);

Solution snippet:

const debouncedSearchTerm = useDebounce(searchTerm, 500);
// use debouncedSearchTerm for network calls