Debouncing in React with useEffect

May 4, 2019

When building React applications, handling user input efficiently is crucial—especially when dealing with search fields or API calls. Without optimization, every keystroke can trigger a new request, leading to performance issues and unnecessary server load.

A common solution to this problem is debouncing—delaying function execution until after a user has stopped typing.

In this post, we’ll cover:
✅ What debouncing is and why it matters in React.
✅ How to implement search with debouncing using useEffect.
✅ Optimizing API calls with debouncing.


What is Debouncing?

Debouncing is a technique that ensures a function is only executed after a delay following the last call. If a function is called multiple times within the delay period, it resets the timer and waits again.

Example Use Case: Search Input

Imagine a search bar that fetches suggestions from an API. Without debouncing, every keystroke would trigger a new API request:

<input onChange={(e) => fetchResults(e.target.value)} />

🔴 Problem: This approach can spam the API, causing performance issues.
✅ Solution: Use debouncing to wait until the user stops typing before making the API request.


Implementing Debouncing with useEffect

We’ll use setTimeout inside useEffect to debounce a search input field.

Step 1: Create a Debounced Search Input

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

function SearchInput() {
  const [query, setQuery] = useState("");
  const [debouncedQuery, setDebouncedQuery] = useState("");

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedQuery(query);
    }, 500); // 500ms debounce delay

    return () => clearTimeout(handler); // Cleanup on re-render
  }, [query]);

  return (
    <div>
      <input
        type="text"
        placeholder="Search..."
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      <p>Searching for: {debouncedQuery}</p>
    </div>
  );
}

export default SearchInput;

Step 2: Optimizing API Calls with Debounced Search

Now, let’s fetch data from an API only when the debounced value changes.

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

function SearchAPI() {
  const [query, setQuery] = useState("");
  const [debouncedQuery, setDebouncedQuery] = useState("");
  const [results, setResults] = useState([]);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedQuery(query);
    }, 500);

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

  useEffect(() => {
    if (debouncedQuery) {
      fetch(`https://api.example.com/search?q=${debouncedQuery}`)
        .then((res) => res.json())
        .then((data) => setResults(data.items));
    }
  }, [debouncedQuery]);

  return (
    <div>
      <input
        type="text"
        placeholder="Search..."
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      <ul>
        {results.map((item, index) => (
          <li key={index}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

export default SearchAPI;

Why This Works

🔹 Debounced input prevents excessive API calls.
🔹 First useEffect waits for user input to settle before updating debouncedQuery.
🔹 Second useEffect triggers the API call only when debouncedQuery changes.


Conclusion

Debouncing is an essential optimization technique for React applications, improving performance and reducing unnecessary API calls.

🚀 Key Takeaways:

  • Use setTimeout inside useEffect to debounce input changes.
  • Always clear the timeout in the cleanup function to prevent memory leaks.
  • Separate state updates from API calls for better efficiency.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *