BlogReactJS

Why cacheTime in React Query should always be bigger than staleTime

Updated by Codemzy on November 21st, 2023

In React Query, the cacheTime and staleTime options impact how long data gets stored on the client and how often it's re-fetched from the server. Let's look at the difference between cacheTime and staleTime - and how changes to one can impact the other.

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 cache
  • cacheTime defaults to 5 minutes
  • staleTime is the duration data is considered fresh - once it's stale any new calls to the query will trigger a re-fetch from the server
  • staleTime defaults to 0

Setting cacheTime and staleTime globally

React Query always assumes data is stale, even if it has just fetched it.

Out of the box, React Query is configured with aggressive but sane defaults. Sometimes these defaults can catch new users off guard or make learning/debugging difficult if they are unknown by the user.

- React Query Important Defaults

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.