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 cacheTime and staleTime, then conclude with a few opinionated recommendations as to how to use it with React Query.

Terminology

As their English names might (very remotely) imply, cacheTime and staleTime are here to allow us to limit the number of HTTP queries sent to our servers and ultimately optimize our users' experience.

More specifically, cacheTime determines for how long a certain response is supposed to stay in cache before it gets garbage collected.

Similarly, 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:

  • staleTime of 1 minute

  • cacheTime of 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 cacheTime .

Stale time should not exceed cache time

Our staleTime shouldn't normally exceed the cacheTime. Both 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 staleTime.

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 (quite pragmatic) (and sometimes polemic) recommendation would be to increase your staleTime to a very short duration, such as 5 seconds, while keeping your cacheTime at a higher level, say 5 minutes, or Infinity 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 Antelo Live 🔴 by becoming a sponsor. Any amount is appreciated!