Instead of debouncing the queryFn, debounce the search parameters. This ensures that the query function is called only when the debounced search parameters change.
First, create a `useDebounce` hook to return a debounced value that updates only after a specified 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; } export default useDebounce;
Here’s an example using a todo list.
import { useState } from "react"; import { useQuery } from "react-query"; import useDebounce from "./useDebounce"; function useTodos(page, search) { let url = `https://jsonplaceholder.typicode.com/todos?_page=${page}`; if (!!search) { url += `&q=${search}`; } return useQuery( ["todos", { page, search }], () => fetch(url).then((res) => res.json()) ); } export default function Todos() { const [page, setPage] = useState(0); const [search, setSearch] = useState(""); const debouncedSearch = useDebounce(search, 500); const { isFetching, isError, data, error } = useTodos(page, debouncedSearch); function handleSearchChange(event) { setSearch(event.target.value); } function handlePageChange(event) { setPage(event.target.value); } const availablePages = [...Array(11).keys()].slice(1); return ( <>{isFetching ? (> ); }Loading...) : isError ? (Error: {error.message}) : ()}
{data.map((todo) => ( # Title Completed? ))} {todo.id} {todo.title} {todo.completed ? "yes" : "no"}
By debouncing the search parameters rather than the `queryFn`, you ensure that useQuery receives a defined value and avoids the issue of a debounced function not returning a value immediately. This approach leverages the built-in caching and state management features of React Query while optimizing performance with debounce.