Skip to main content
All Docs
FeaturesBlockManOSUpdated March 26, 2026

Reducing Unnecessary Data Refetches with QueryClient staleTime (PERF-06)

Reducing Unnecessary Data Refetches with QueryClient staleTime (PERF-06)

Version introduced: v1.0.83
Category: Performance · Frontend
File affected: src/lib/trpc/provider.tsx


Background

The platform dashboard loads data for multiple domains simultaneously — developments, compliance obligations, service charges, owner records, and more. Each of these domains maps to one or more tRPC queries executed by React components.

Prior to v1.0.83, TRPCProvider initialised QueryClient with no options:

const queryClient = new QueryClient();

React Query's default staleTime is 0ms, which means cached data is considered stale the instant it arrives. Every time a component mounts — including on routine tab or route navigation — React Query schedules a background refetch, even if the data was fetched moments ago.

For a dashboard with many concurrent queries this produces a waterfall of redundant network requests on every navigation event.


The Fix

QueryClient is now initialised with sensible global defaults in TRPCProvider:

// src/lib/trpc/provider.tsx

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 60 * 1000,      // data stays fresh for 1 minute
      gcTime:    5 * 60 * 1000,  // inactive cache entries live for 5 minutes
    },
  },
});
OptionValueEffect
staleTime60 000 ms (1 min)Queries will not refetch on mount if data was loaded within the last minute
gcTime300 000 ms (5 min)Cached data is retained in memory for 5 minutes after the last subscriber unmounts

Fine-Tuning for Reference Data

Some data sets change very infrequently — the developments list, compliance obligation templates, and unit type configurations are good examples. These can be given a longer staleTime of 5 minutes either globally via setQueryDefaults or per-query:

Using setQueryDefaults

// After creating queryClient, before passing it to TRPCProvider
queryClient.setQueryDefaults(
  ['developments', 'list'],
  { staleTime: 5 * 60 * 1000 }
);

queryClient.setQueryDefaults(
  ['compliance', 'obligations'],
  { staleTime: 5 * 60 * 1000 }
);

Using a per-query option

const { data } = trpc.developments.list.useQuery(undefined, {
  staleTime: 5 * 60 * 1000,
});

Expected Behaviour After This Change

  • Tab navigation: Switching between dashboard sections (e.g. Compliance → Service Charges → Developments) will no longer trigger refetches for queries that were loaded within the last minute.
  • Component remounts: Queries fetched during the current navigation session are reused from cache until staleTime expires.
  • Background refresh: React Query still silently refetches in the background once staleTime elapses, keeping data up to date without blocking the UI.
  • Cache eviction: Unused query data is removed from memory after 5 minutes, preventing unbounded cache growth.

Trade-offs

ConcernMitigation
Users may see data that is up to 1 minute oldstaleTime only prevents a background refetch, not a foreground one. Explicitly calling refetch() or invalidating the query still fetches fresh data immediately.
Mutations should invalidate affected queriesUse utils.invalidate() after any mutation that changes data covered by a cached query, as normal.
Real-time sensitive dataFor queries that must always be current (e.g. payment status), override staleTime: 0 at the call site.