React Query: cacheTime vs staleTime
If you ran upon this thread, you're probably struggling with setting up a great caching strategy using React Query (very recently renamed to TanStack Query). In this short article, I'll try to clarify the most common sources of confusion around
staleTime, then conclude with a few opinionated recommendations as to how to use it with React Query.
As their English names might (very remotely) imply,
staleTime are here to allow us to limit the number of HTTP queries sent to our servers and ultimately optimize our users' experience.
cacheTime determines for how long a certain response is supposed to stay in cache before it gets garbage collected.
staleTime determines for how long a certain response will still be considered fresh (or not stale), dismissing the need for a new request.
To rephrase it, then,
cacheTime relates to the expiration of a specific value, while
staleTime will be expiring the validity of a certain query.
React Query defaults
If not set otherwise, React Query comes out of the box with a
cacheTime of 5 minutes and a
staleTime set to zero. This is very important to keep in mind!
If you want to dig deeper, I do advise you to visit their docs. They have a dedicated page for caching examples and, overall, very didactic explanations.
But what does it mean concretely? It means that, by default, React Query will always cache your request responses for up to 5 minutes after they've been done. The advantage is that, when the user tries to fetch from the back-end a second time, React Query will serve the last value almost instantly.
However, as our default for
staleTime is set to zero, React Query will consider our request as stale (or not fresh) and trigger the second call in the background. And, once the call is finished, it will update our state with the freshly new data coming from the back-end.
The advantage of using this strategy is the responsiveness provided to our users. We first optimistically provide them with the values we already have stored from previous requests. Then, in the background, we'll fetch the new data and update the UI if needed.
Most of the time, of course, we expect the data to not have changed. So the second query that is happening in the background could have been avoided. How can we do that? That where
staleTime comes in handy! Let's see.
What happens when we increase the staleTime?
To illustrate, imagine we've changed React Query defaults to match:
staleTimeof 1 minute
cacheTimeof 5 minutes
Now let's go through a detailed timeline:
At 00:00:00 a user fetches, for instance, our index of posts. At this moment, the response returned by our back-end will be stored by React Query and set to expire in 5 minutes. The query, on the other hand, will be considered to stay fresh for the next 1 minute.
At 00:00:30 the same user interacts with the interface, triggering a new query to posts. As our cache is still valid, we'll serve it to the user. Secondly, as the query is still fresh, we'll NOT trigger a background call to update it.
Four minutes later, at 00:04:30, the same user triggers the same call. As our cache is still valid, we will again serve it to the user. Secondly, as the query is now considered stale, React Query will have to trigger a background call. Once the response is returned, React Query will update the data provided to the user, and store the new value on the cache for the next 5 minutes.
If that makes sense to you, let's try to draw it in a simple decision tree.
How these decisions are taken by React Query?
A back-of-the-napkin draft of cache and stale times decision-making process
The illustration is not quite UML-compliant, but it should provide a nice overview of what the process looks like. In a very simplistic representation of reality, the following questions are the two main things React Query will try to answer while handling your queries:
Q-1: Will the cached data be reused in the component? Yes, if
cacheTime preserves the data from garbage collection and allows it to be reused.
Q-2: Will an HTTP request to refresh the data be triggered? Yes, but only if
staleTime determines that the query is NOT fresh any longer. In this case, a new HTTP request will be sent to refresh the data. It will happen in the background.
My own recommendations
Stick to the default values when you can
First of all, most of the time, prefer to stick with the defaults. Especially for more complex user interfaces, keeping
staleTime down to zero will prevent you from having to worry about invalidating queries all over the place.
Keep your cache time high in any case
If, and only if, (1) your UI tends to trigger multiple successive calls to the same endpoint, and (2) your servers would not be able to handle successive calls to that endpoint, and (3) its data tends to be stable, not changing over time. With all those conditions in mind, if increasing
staleTime still makes sense to you, then go ahead, but keep in mind that it should normally NOT exceed the
Stale time should not exceed cache time
staleTime shouldn't normally exceed the
staleTime = cacheTime and
staleTime > cacheTime will behave exactly the same. That's because, if the currently cached value has expired, then a new request will already have to be triggered anyway, dismissing the back-and-forth about the
For very heavyweight queries that are too tricky to invalidate, just slightly increasing the staleTime might do the job 😏
First, let's detail under which conditions this might make sense:
(1) UI tends to trigger multiple successive calls to the same endpoint
(2) servers would not be able to handle successive calls to that endpoint.
Just make sure to think about this twice. Many devs tend to over-optimize things way too early. If this query has never been an issue, then it's probably too early to optimize. Your time will be better invested in something else.
(3) returned data tends to be changed very often
(4) this endpoint depends on too many resources, making your cache invalidation become too risky and error-prone
In this case, my opinionated (and quite pragmatic) (and sometimes polemic) recommendation would be to increase your
staleTime to a very short duration, such as 20 seconds, while keeping your
cacheTime at a higher level, say 5 minutes, or Infinite if you wish, that shouldn't make much of a difference anyway.
This specific setup should allow you to substantially reduce the number of successive calls triggered by sequential mounts and unmounts of components. At the same time, it prevents developers from having to hunt down every single edge case where cache invalidation is needed.
Other frequent misconceptions
On multiple occasions, I've seen pull requests setting the cache time to very large and specific values, such as 1 month. This is not useful. Keep in mind that, with React Query, the cache lives in memory, within your application, which means there is NO server or browser caching involved, which means that the cached values will be lost when the user refreshes their browser. With that in mind, one might decide to use an Infinite value, for the sake of simplicity, but it does not make much sense to use values such as 1 month, 1 week, or even 1 day. That is, unless your users actually keep your application open without any full page refresh for that long.
Did you find this article valuable?
Support Flavio Wuensche by becoming a sponsor. Any amount is appreciated!