Readers like you help support MUO. When you make a purchase using links on our site, we may earn an affiliate commission. Read More.

In React, when implementing the search functionality, the onChange handler calls the search function every time the user types inside the input box. This approach can cause performance issues, especially if making API calls or querying the database. Frequent calls to the search function may overload the web server, leading to crashes or unresponsive UI. Debouncing solves this problem.

What Is Debouncing?

Typically, you implement the search functionality in React by calling an onChange handler function on every keystroke as shown below:

 import { useState } from "react";

export default function Search() {
  const [searchTerm, setSearchTerm] = useState("");

  const handleSearch = () => {
    console.log("Search for:", searchTerm);
  };

  const handleChange = (e) => {
    setSearchTerm(e.target.value);
    // Calls search function
    handleSearch();
  };

  return (
    <input
      onChange={handleChange}
      value={searchTerm}
      placeholder="Search here..."
    />
  );
}

While this works, the call to the backend to update search results on every keypress can get expensive. For example, if you were searching for “webdev”, the application would send a request to the backend with the values “w”, “we”, “web”, and so on.

Debouncing is a technique that works by delaying the execution of a function until a delay period has elapsed. The debounce function detects every time the user types and prevents the call to the search handler until the delay has elapsed. If the user continues typing within the delay period, the timer is reset and React calls the function again for the new delay. This process continues until the user pauses typing.

By waiting for users to pause typing, debouncing ensures your application makes only the necessary search requests thereby reducing server load.

How to Debounce Search in React

There are several libraries you can use to implement debounce. You can also choose to implement it yourself from scratch using JavaScript’s setTimeout and clearTimeout functions.

This article uses the debounce function from the lodash library.

Assuming you have a React project ready, create a new component called Search. If you don’t have a working project, create a React app using the create React app utility.

In the Search component file, copy the following code to create a search input box that calls a handler function on each keystroke.

 import { useState } from "react";

export default function Search() {
  const [searchTerm, setSearchTerm] = useState("");

  const handleSearch = () => {
    console.log("Search for:", searchTerm);
  };

  const handleChange = (e) => {
    setSearchTerm(e.target.value);
    // Calls search function
    handleSearch();
  };

  return (
    <input
      onChange={handleChange}
      value={searchTerm}
      placeholder="Search here..."
    />
  );
}

To debounce the handleSearch function, pass it to the debounce function from lodash.

 import debounce from "lodash.debounce";
import { useState } from "react";

export default function Search() {
  const [searchTerm, setSearchTerm] = useState("");

  const handleSearch = () => {
    console.log("Search for:", searchTerm);
  };
  const debouncedSearch = debounce(handleSearch, 1000);

  const handleChange = (e) => {
    setSearchTerm(e.target.value);
    // Calls search function
    debouncedSearch();
  };

  return (
    <input
      onChange={handleChange}
      value={searchTerm}
      placeholder="Search here..."
    />
  );
}

In the debounce function, you're passing in the function you want to delay i.e. the handleSearch function, and the delay time in milliseconds i.e., 500ms.

While the above code should delay the call to the handleSearch request until the user pauses typing, it doesn't work in React. We’ll explain why in the following section.

Debouncing and Rerenders

This application uses a controlled input. This means the state value controls the value of the input; every time a user types in the search field React updates the state.

In React, when a state value changes, React rerenders the component and executes all the functions inside it.

In the above search component, when the component re-renders, React executes the debounce function. The function creates a new timer that keeps track of the delay and the old timer sits in the memory. When its time elapses, it fires the search function. This means that the search function is never debounced, it's delayed by 500ms. This cycle repeats on every render— the function creates a new timer, the old timer expires and then it calls the search function

For the debounce function to work, you have to call it only once. You can do this by calling the debounce function outside the component or by using the memoization technique. This way, even if the component rerenders, React will not execute it again.

Defining the Debounce Function Outside the Search Component

Move the debounce function outside the Search component as shown below:

 import debounce from "lodash.debounce"

const handleSearch = (searchTerm) => {
  console.log("Search for:", searchTerm);
};

const debouncedSearch = debounce(handleSearch, 500);

Now, in the Search component, call debouncedSearch and pass in the search term.

 export default function Search() {
  const [searchTerm, setSearchTerm] = useState("");

  const handleChange = (e) => {
    setSearchTerm(e.target.value);
    // Calls search function
    debouncedSearch(searchTerm);
  };

  return (
    <input
      onChange={handleChange}
      value={searchTerm}
      placeholder="Search here..."
    />
  );
}

The search function will only be called after the delay period elapses.

Memoizing the Debounce Function

Memoizing refers to caching the results of a function and reusing them when you call the function with the same arguments.

To memoize the debounce function, use the useMemo hook.

 import debounce from "lodash.debounce";
import { useCallback, useMemo, useState } from "react";

export default function Search() {
  const [searchTerm, setSearchTerm] = useState("");

  const handleSearch = useCallback((searchTerm) => {
    console.log("Search for:", searchTerm);
  }, []);

  const debouncedSearch = useMemo(() => {
    return debounce(handleSearch, 500);
  }, [handleSearch]);

  const handleChange = (e) => {
    setSearchTerm(e.target.value);
    // Calls search function
    debouncedSearch(searchTerm);
  };

  return (
    <input
      onChange={handleChange}
      value={searchTerm}
      placeholder="Search here..."
    />
  );
}

Note that you’ve also wrapped the handleSearch function in a useCallback hook to ensure React only calls it once. Without the useCallback hook, React would execute the handleSearch function with every re-render making the dependencies of the useMemo hook change which in turn would call the debounce function.

Now, React will only call the debounce function if the handleSearch function or the delay time changes.

Optimize Search With Debounce

Sometimes, slowing down can be better for performance. When handling search tasks, especially with expensive database or API calls, using a debounce function is the way to go. This function introduces a delay before sending backend requests.

It helps reduce the number of requests made to the server, as it only sends the request after the delay has elapsed and the user has paused typing. This way, the server doesn't get overloaded with too many requests, and performance remains efficient.