I've recently been learning React Query and feel like I am a little late to the party. But you know what they say, better late than never!
One of the early "gotchas" with React Query is how aggressively it re-fetches data. The moment your data arrives from the server, it's stale - according to React Query.
And that means it will re-fetch for fresh data whenever the user returns to the data. So you might find that users are making multiple network requests for the same data.
And you can adjust this, if you like, with staleTime
.
What is staleTime
?
staleTime
is the length of time before your data becomes stale. The default value in React Query is staleTime: 0
- which means your data is immediately stale!
In React Query, your data can be fresh or stale.
If it's fresh, the saved (cached) data will be used repeatedly, without more API calls to the server.
If data is stale, then fresh data gets fetched anytime the window refocuses, the component re-mounts or the network reconnects.
staleTime
is 0 by default, so data is never fresh. If you have server data that changes often, that's what you want. But if your data is fairly static, you might want to extend the staleTime
on your query.
E.g. If you only want to check back with the server after at least 10 minutes, you can extend the staleTime
.
const { data } = useQuery(['my-data'], fetchMyData, {
staleTime: 10 * (60 * 1000), // 10 mins
});
In the example above, React Query will fetch the data with useQuery
and store it in the cache with the key my-data
. Because we've set the staleTime
the data is fresh for 10 minutes.
When data is fresh, instead of fetching the data from the server again, React Query will keep using the data in the cache.
What is cacheTime
?
cacheTime
is the length of time before inactive data gets removed from the cache. The default value in React Query is cacheTime: 300000
- which is 5 minutes.
Unlike staleTime
, cacheTime
only comes into action when you are no longer using the query. The cacheTime
countdown begins after the query becomes inactive.
In React Query, if all of the components that use a query are unmounted, then the query becomes inactive. Inactive data gets removed from the cache (deleted and garbage collected).
Even if your data is stale, rather than showing nothing, React Query can give you the cached data while it fetches fresh data from the server.
Usually, this is 5 minutes. And you probably don't need to mess around with this value very often - unless your staleTime
is longer than 5 minutes.
Why should cacheTime
be bigger than staleTime
?
Let's go back to our earlier example, where we set staleTime
to 10 minutes.
const { data } = useQuery(['my-data'], fetchMyData, {
staleTime: 10 * (60 * 1000), // 10 mins
});
Say you set staleTime
to 10 minutes because the data rarely changes, and you're happy for users to use data from this request for 10 minutes at least. It will save extra network requests and server resources because the data isn't likely to change within those 10 minutes.
And when it does change, it's by the user, so you can handle those changes (or invalidate the query when they happen) without heading back to the server.
Happy times!
But wait, the cacheTime
is still the default - 5 minutes.
If the user navigates away from the page using this query and returns after 6 minutes, the data should still be fresh. You would be happy to use the data from the cache.
But sadly, it's gone! That lovely fresh data - which had 4 minutes of freshness left - is deleted.
And your query will go back to isLoading
state.
That means a loading spinner (probably) and another call to the server - just to get the same data back you had before!
It doesn't make sense to throw away data if you still consider it fresh, so that's why cacheTime
should always be bigger than staleTime
.
const { data } = useQuery(['my-data'], fetchMyData, {
staleTime: 10 * (60 * 1000), // 10 mins
cacheTime: 15 * (60 * 1000), // 15 mins
});
Since the default settings have cacheTime
5 minutes longer than staleTime
, I tend to stick with that.
cacheTime
vs staleTime
There can be some confusion between cacheTime
and staleTime
, and while both these options relate to the data in the cache, they control different things about your data.
For a quick recap:
cacheTime
is the duration React Query stores inactive data before it is deleted from the cachecacheTime
defaults to 5 minutesstaleTime
is the duration data is considered fresh - once it's stale any new calls to the query will trigger a re-fetch from the serverstaleTime
defaults to 0
Setting cacheTime
and staleTime
globally
React Query always assumes data is stale, even if it has just fetched it.
For some data - data that frequently changes from multiple sources - those defaults work well. You want to keep checking with the server to get the latest updates. But if you have stable data that rarely changes, you might want to reduce network requests and avoid continuously fetching the same information.
We've seen how you can extend the staleTime
to keep your data fresh for longer and reduce re-fetches per query. But you can also adjust this setting globally.
So if you want all of your data to be considered fresh for longer - you can! Instead of changing cacheTime
and staleTime
on useQuery
, you'll set it on the QueryClient
.
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5*(60*1000), // 5 mins
cacheTime: 10*(60*1000), // 10 mins
},
},
});
And all of your useQuery
requests will have a staleTime
of 5 minutes and a cacheTime
of 10 minutes.
If you have any requests that need shorter settings, you can still adjust the cacheTime
and staleTime
options on those individual queries (useQuery
) to override your new global settings.
There's always exceptions
Ok, so I used always in the blog title, and maybe I was a little hasty.
After reading an interesting comment by React Query master TkDodo, there was a suggestion you could have staleTime: Infinity
, cacheTime: 0
. That's staleTime
bigger than cacheTime
to the extreme!
And that doesn't mean the data always stays fresh, even though staleTime
is infinity! It means the data is fresh for as long as the query is used. And gets garbage collected (by cacheTime
) as soon as it's inactive.
And you might be wondering the difference between:
{
staleTime: Infinity,
cacheTime: 0,
}
And:
{
staleTime: 0,
cacheTime: 0,
}
The difference is that in the first case, even if you go away from the window and come back - the data won’t re-fetch. Because the query is still in use, the cacheTime
isn’t triggered. And staleTime
is infinity - so the data is still considered fresh - no need for a re-fetch.
But with the second example, a background re-fetch will happen, triggered by the window refocus. Because staleTime
is 0
, and the data is considered stale. The query will also go into a loading state if there is no cached data because cacheTime
is 0
so the query is garbage collected as soon as it's inactive.
If staleTime
is longer than cacheTime
, it means that your query is more likely to go back into a loading state when your data goes stale. But it won’t always - only if the query wasn’t being used.
If the query is still active, cacheTime
won't get triggered.
But if the query is inactive, and cacheTime
is smaller than staleTime
, then it’s likely the data might have been garbage collected before it went stale. That means you'll get a loading state while fresh data is fetched because there's no stale data to show. It's gone!
But if there is a case where you want the query to stay fresh while the query is in use, but get removed from the cache more quickly when it’s not in use, you could set the cacheTime
lower than the staleTime
.
I guess the situation for this would be:
- You don’t want the data to be background re-fetched
- You don’t want stale data to show when you do a re-fetch
- But you do want to data to be re-fetched if the query is not used and then used again
You could also set refetchOnWindowFocus
and refetchOnReconnect
to false to prevent the query re-fetch triggering while the query is in use, but if you want to force that loading state back and not show stale data, that’s when cacheTime
should be shorter.
Personally, if my data isn’t stale yet, even if the query isn’t in use, I wouldn’t want it to be removed from the cache - which is why I always have cacheTime
bigger than staleTime
.
But perhaps your data has to be accurate to the millisecond, and you don't want stale data to be around while you’re re-fetching fresh information. Then maybe your cacheTime
might need to be smaller than staleTime
.