In React Query, two key options control the freshness and caching duration of query results: staleTime and gcTime (formerly cacheTime).
Here’s a brief explanation of each:
staleTime: The duration a query result is considered fresh. While fresh, data is read from the cache only, with no network requests. Once stale (default is instantly), data is still read from the cache, but a background refetch can happen under certain conditions.gcTime: The duration inactive queries remain in the cache before being removed. This defaults to 5 minutes. Queries become inactive when no components are observing them.Typically, you may need to adjust
staleTimemore often thangcTime.
To understand staleTime, consider a simple example of a hook to fetch a single vehicle:
export const useFetchVehicle = (vehicleId: string | undefined) => {
const [vehicle, setVehicle] = useState<Vehicle | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const init = async () => {
try {
const data: Vehicle = await fetchVehicle(vehicleId);
setVehicle(data);
} catch (err) {
setError((err as Error).message);
} finally {
setLoading(false);
}
};
init();
}, [vehicleId]);
return { vehicle, loading, error };
};Using React Query, we can simplify this with the useQuery hook, eliminating the need for useState and useEffect:
import { useQuery } from '@tanstack/react-query’;
export const useFetchVehicle = (vehicleId: string | undefined) => {
return useQuery({
queryKey: ['vehicle', vehicleId],
queryFn: () => fetchVehicle(vehicleId),
});
};staleTime of 0In the above code, useQuery manages the loading, error, and data states. The queryKey uniquely identifies the query, and queryFn fetches the data. Since staleTime is not set, it defaults to 0.
Notice how selecting the first item triggers an API call for /vehicle/1. Returning to the list and selecting the first item again triggers another API call. The UI renders immediately with cached data, but a background refetch happens due to the default staleTime of 0.

In DevTools, ['vehicle', '1'] is considered stale immediately with staleTime set to 0.
A background refetch can happen when:
Read more from Important Defaults.
staleTime to another valueConsider setting staleTime to 30 seconds (30 * 1000 milliseconds):
export const useFetchVehicle = (vehicleId: string | undefined) => {
return useQuery({
queryKey: ['vehicle', vehicleId],
queryFn: () => fetchVehicle(vehicleId),
+ staleTime: 30 * 1000,
});
};Now, selecting the first item triggers an API call for /vehicle/1. Returning to the list and selecting the first item again does not trigger an API call. The UI renders immediately with cached data, considered fresh for 30 seconds, with no background refetch.

In DevTools, ['vehicle', '1'] is considered fresh for 30 seconds with staleTime set to 30 seconds.
staleTime is 0Quoted from UI Dev Data Synchronization.
The default staleTime of 0 means every query is instantly considered stale. This “aggressive but sane default” ensures frequent refetching, which is generally safer than fetching too infrequently.
The docs define this as “aggressive but sane defaults”.
Aggressive, because it means we might be refetching from the server more often than we need to, but sane because fetching too often is the lesser evil of the two options.
It’s a bit like re-renders in React. Yes, we all want to minimize our application’s re-renders, but having too many is significantly better than having too little where your view could be out of sync with your application’s state.
Also, if the default value weren’t 0, what would be a better default? 20 seconds? 30? 1 minute? It’s one of those cases that you can’t reliably set up for every possible situation. The answer is always it depends.
Specifically, it depends on the resource in question: How often is it updated? How accurate does the data displayed on your screen need to be? How collaborative is the environment you’re working in?
The answer to these questions should be decided by developers on a case by case basis.
If we fetch a Twitter post with all its likes and comments, it’s likely stale pretty fast. On the other hand, if we fetch exchange rates that update on a daily basis, well, our data is going to be quite accurate for some time even without refetching.
cacheTime does nothing while a query is in use. It activates once the query becomes unused, removing data from the cache after the specified duration (default is 5 minutes).
The time in milliseconds that unused/inactive cache data remains in memory. When a query’s cache becomes unused or inactive, that cache data will be garbage collected after this duration. When different garbage collection times are specified, the longest one will be used.
5 * 60 * 1000 (5 minutes) or Infinity during SSR.Infinity, garbage collection is disabled.gc refers to “garbage collect” time, a common term in computer science.For example, the ['vehicle', '1'] query is removed from the cache after 5 minutes of inactivity.
In React Query v5, the
cacheTimeoption has been renamed togcTime.
I recently gave a 30-minute version of my talk, Creating Fast-Feeling Web Apps, at JSConfJP in Tokyo, one of the largest web developer events in Japan. The feedback from attendees was fantastic. During the talk, I covered topics like staleTime in React Query and how to use initialData to deliver an instant user experience. You can check out the slides and the recorded talk for more details