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
staleTime
more 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 0
In 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 0
Quoted 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
cacheTime
option 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