Debouncing while using React Query

React Query is the most popular React library for managing async queries. It allows you to fetch data declaratively and handles caching and re-fetching of data. Debouncing will enable you to avoid making a query more times than necessary. For example a search form on a web app, debouncing prevents making a request to the server on every keystroke.

import {
  useQuery,
} from '@tanstack/react-query'

function Search() {
    const [searchText, setSearchText] = useState("");

    const searchQuery = useQuery(
        ["posts", "search", searchText],
        () => fetch(...).then(...);
    );

    return (
        <input value={searchText} onChange={() => setSearchText(...)} name="search" />
        <ul>
            {/* render list of results */}
        </ul>
    )
}

In the above component, a request is made to the server on every keystroke, this puts an unnecessary strain on the server. To avoid this we can use two approaches:

  • Only make a request when the user presses Enter
  • Wait for the user to type a little bit then make a request.

Only make a request when a user presses Enter

We can do this by wrapping the input in a form and only updating the search text in the onSubmit handler of the form.


...

<form onSubmit={
(e) => {
e.preventDefault();
setSearchText(e.target.search.value);
}
}>
  <input name="search" />
</form>
...

In the example above, setSearchText is only set when the user presses the Enter key, thereby triggering React Query to fetch results from the server.

Wait for the user to type a little bit then make a request

For this, we need to use useEffect and setTimeout to only update the search term after a given time has passed. We cancel any timeouts once the user types.

...

function Search() {
    const [searchText, setSearchText] = useState("");
    const [input, setInput] = useState("");

    const searchQuery = useQuery(
        ["posts", "search", searchText],
        () => fetch(...).then(...);
    );

useEffect(
() => {
  const id = setTimeout(() => setSearchText(input), 300);
  return () => clearTimeout(id);
},
[input]);

    return (
        <input value={searchText} onChange={() => setInput(...)} name="search" />
        <ul>
            {/* render list of results */}
        </ul>
    )
}

In the code above, we add a new state variable, input using useState. This will be used to hold the actual value the user types in. We then update the onChange event handler to use setInput. This will store what the user types on every keystroke.

Next, we add a useEffect that will run a given function whenever input changes on every keystroke. In the function, we use setTimeout to run a given function that updates setSearchText after 300 milliseconds. When the user types a character, a new timeout is created that will run the function after 300 milliseconds. If they type another character, useEffect will call the function in the return which will clear the previous timeout. This ensures we only update setSearchText after the user finishes typing.