Debouncing in React

What is Debouncing ?

Debouncing is the process of removing unwanted user input. For example, you want to run a function only once in spite of it being triggered multiple times. The most common use case of debouncing is when you want to trigger an API call only after the user stops typing.

Here is an example of how to do it in React. I want to search through multiple films. There is one search text box. Once I am done typing the name of the film, only then the API should be called.

Entire Code

import logo from "./logo.svg";
import "./App.css";
import { useEffect, useRef, useState } from "react";
import { searchFilms } from "./requests";
import Film from "./Film";
function App() {
  const [films, setFilms] = useState([]);
  const [allFilms, setAllFilms] = useState([]);
  const [searchText, setSearchText] = useState("");
  const [debouncedText, setDebouncedText] = useState("");
  const ref = useRef();

   //Called once when the component loads. 
  useEffect(function onLoad() {
    //API call to load movies. When no input is given, it returns all movies.
    searchFilms().then((res) => {
      if (Array.isArray(res.results) && res.results.length > 0) {
        //Store all movies as current result 
        setFilms(res.results);
        // Store all movies for backup
        setAllFilms(res.results);
      }
    });
  }, []);

  useEffect(
    function searchHandler() {
      if (debouncedText) {
        searchFilms(debouncedText).then((res) => {
          if (Array.isArray(res.results)) {
            setFilms(res.results);
          }
        });
      } else {
        setFilms(allFilms);
      }
    },
    [debouncedText]
  );

  //Debounce Handler
  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedText(searchText);
    }, 1000);

    //Clears the timeout 
    return () => {
      clearTimeout(handler);
    };
  }, [searchText]);

  return (
    <div className="App">
      <input
        type="text"
        ref={ref}
        placeholder="Search"
        onChange={(e) => setSearchText(e.target.value)}
      />
      {films.map((film) => (
        <Film film={film} />
      ))}
      {films.length === 0 && <p>No Films Found </p>}
    </div>
  );
}

export default App;

Let me explain the most important part of the code. Here's the debounce handler :

Debounce Handler Explaination

 //Debounce Handler
  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedText(searchText);
    }, 1000);

    //Clears the timeout 
    return () => {
      clearTimeout(handler);
    };
  }, [searchText]);

The handler is supposed to execute a second after searchText changes. However, as soon as searchText is updated, the previous timeout is cleared and a new timeout is created. As a result, while searchText is getting updated on every keystroke, debouncedText is updated only a second after the last keystroke is completed.

Once debounceText is updated, API call is triggered from this useEffect :

Final API Call

  useEffect(
    function searchHandler() {
      if (debouncedText) {
        searchFilms(debouncedText).then((res) => {
          if (Array.isArray(res.results)) {
            setFilms(res.results);
          }
        });
      } else {
        setFilms(allFilms);
      }
    },
    [debouncedText]
  );