Instead of debouncing the fetch function directly, you should debounce the query parameters (e.g., the search value) so that the queryKey changes only after a delay. This way useQuery is called only when the input has stabilized, not on every keystroke.

1. Create a useDebounce Hook

This hook will delay the value and only set the debounced value after the specified delay:

function useDebounce(value, delay) {  const [debouncedValue, setDebouncedValue] = useState(value);  useEffect(() => {

    const handler = setTimeout(() => setDebouncedValue(value), delay);

    return () => clearTimeout(handler);

  }, [value, delay]);

  return debouncedValue;

}

2. Use Debounced Value in Your Query Component

Now use useDebounce with useQuery like this:

const debouncedSearch = useDebounce(search, 500);

const { data, isLoading, isError } = useQuery({

  queryKey: [‘todos’, page, debouncedSearch],

  queryFn: () => fetch(`/api/todos?page=${page}&q=${debouncedSearch}`)

               .then(res => res.json()),

  enabled: !!debouncedSearch,

});

This way the query will only run after the debounced value changes

Also ReadHow Much Does It Cost to Hire Remote React Developers?

Why This Works

  • Debouncing the queryKey instead of the query function matches React Query’s caching and lifecycle logic.
  • useQuery will only trigger when debouncedSearch changes, not on every keystroke.
  • You still get all the benefits of caching, isLoading, isError and other React Query states