HomeWhen You Might Need to Override the Defaults in TanStack Query

When You Might Need to Override the Defaults in TanStack Query

April 26, 20253 min read

TanStack Query comes with some aggressive defaults (you can see an overview of all the defaults here). Most times these defaults work perfectly and you never need to worry about overriding them—that's the beauty of the library.

However, there are times when you might want to override these defaults. Let's talk about them.

Query Retries

By default, queries initiated by useQuery or useInfiniteQuery retry failed requests 3 times, with an exponential backoff applied for each subsequent retry. A common override is turning off retries for unit or integration tests—this works assuming you have a separate query client for your production and test environments.

You can disable retries by passing false or 0 to the retry option of a specific query or globally in the QueryClient.

Why is this a good idea? Leaving the default retry option on for tests can add significant time to your unit or integration tests, which slows down the feedback loop—especially for larger applications. These tests usually verify that we handle HTTP errors correctly and display appropriate error states. Waiting for three retries before checking these error states adds no value.

The retry option also accepts a callback function that receives the failureCount and error of a query. This callback allows you to selectively retry queries based on the error status code returned from the server.

A common way I have seen this callback used is to disable retries for:

  • 404 Not Found - Retrying these errors is often redundant since the resource likely doesn't exist.
  • 403 Forbidden & 401 Unauthorized - Retrying these authentication errors is unnecessary—instead, it's better to handle them with a logout function in the global error handlers

You can see examples of all these approaches in open source repos here.

I have also worked in codebases where retries are only enabled for network errors—you can see an example of this in an open source project here.

The general pattern is to identify specific errors or error codes you want to retry and override the default with a more fine-grained approach.

Query Caching

By default, Query instances created via useQuery or useInfiniteQuery consider cached data as stale. This means queries are refetched automatically quite aggressively.

Sometimes you may not want this behavior. For example, when integrating with an external service outside your control that has very aggressive rate limits—one example from work is the HMRC API.

Another example is when the data you fetch rarely or never changes after the initial request—there's no need to waste requests on static data. I've encountered this when fetching OAuth scopes or a list of app integrations, which typically remain stable for long periods.

There are several options to configure the cache lifetime of a query. For the most strict option—when you don't want any refetching or reloading after the initial request succeeds—you can use this:

const { isPending, error, data } = useQuery({
    queryKey: ['repoData'],
    queryFn: api.getIntegrations,
    //Ensures that the data is never considered stale
    staleTime: Number.POSITIVE_INFINITY,
    //Ensures that the data is never removed form the cache
    gcTime: Number.POSITIVE_INFINITY, 
    //Prevent refetch on widnow focus
    refetchOnWindowFocus: false,
  })

Your mileage may vary, and there are other interesting options you can configure like refetchOnMount and refetchInterval. These parameters let you fine-tune how often refetches occur.

Here's a long list of different open source projects and how they use these config options—you can learn quite a bit by studying their implementations.

These are some of the defaults I have seen overridden and tweaked in the production apps I've worked on so far. I'd love to hear about other defaults you've customised as well.