Guides
Important defaults
When raf-query fetches, refetches, retries and garbage-collects, which behaviors are on by default, and which options change it.
A client from createQueryClient() caches, dedupes and garbage-collects, and nothing else. Retries, refetching on focus and reconnect, and polling are links and features you pass to the client. recommended() returns the set most apps want:
export function function makeQueryClient(): QueryClient<{
readonly meta: Flat<Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<RetryMeta>>;
}>
makeQueryClient() {
return createQueryClient<readonly [Link<{
readonly meta: RetryMeta;
}>], readonly [Feature<{
readonly meta: FocusRefetchMeta;
}>, Feature<{
readonly meta: ReconnectRefetchMeta;
}>, Feature<{
readonly meta: PollingMeta;
}>]>(config?: QueryClientConfig & {
readonly features?: readonly [Feature<{
readonly meta: FocusRefetchMeta;
}>, Feature<{
readonly meta: ReconnectRefetchMeta;
}>, Feature<...>] | undefined;
readonly links?: readonly [...] | undefined;
}): QueryClient<...>
Create a query client. Behaviors are opt-in: pass links and features,
or spread recommended(). The client's meta type is what they declare.
createQueryClient({ ...function recommended(): RecommendedThe behaviors most apps want: retries, refetching on focus and reconnect,
and polling. Spread it into the config:
recommended(), QueryClientConfig.staleTime?: number | undefinedDefault staleTime: 0.
staleTime: 30_000 });
}| Part | Kind | What it does |
|---|---|---|
retry() | link | Retries failed requests. See Retries |
focusRefetch() | feature | Refetches read, stale queries when the window regains focus |
reconnectRefetch() | feature | Refetches read, stale queries when the browser comes back online |
polling() | feature | Refetches read queries on a timer set by meta.refetchInterval |
Pass links and features yourself to pick some of them, or to add your own. Query meta covers how they're configured per query.
Keep these in mind as you read the rest of the guides. Retries, focus and reconnect refetching, and polling assume recommended():
-
Reading a query never refetches stale data.
useQueryreads what's in the cache: a component that mounts with stale data renders it and starts no request, unlike TanStack Query'suseQuery, which refetches stale data on mount. Loaders decide freshness: every load entry refetches stale data when a loader starts it (prefetchandswrin the background,revalidatebefore it resolves). Stale data refreshes:- when the next loader or server component starts the query with
load, which TanStack Router does on every navigation - when the query is invalidated, such as at the end of an action that writes data
- on window focus and on reconnect, for queries being read, with
focusRefetch()andreconnectRefetch() - on a timer, with
polling()andmeta.refetchInterval(Polling)
Rendering the same handle in ten components reads one cache entry.
- when the next loader or server component starts the query with
-
Cached data is stale immediately (
staleTime: 0). -
Polling is per query.
meta: { refetchInterval }refetches on a timer while a component reads the query, independent ofstaleTime. See Polling. -
Unread entries are kept for five minutes (
gcTime: 300_000). An entry nobody reads is inactive: it stays in the cache in case it's needed again, then is garbage-collected. -
Failed requests retry three times, with a delay that doubles from one second, up to 30 seconds, before the error reaches the UI. Retries wait while the browser is offline. Thrown
Responses aren't retried. -
Data is structurally shared. When a refetch returns data that's deeply equal to what's cached, the cache keeps the old objects, so unchanged parts keep their identity and nothing re-renders. See Render optimizations.
On the server
A server client (one per request) renders once, so it behaves differently:
- Cached data is reused, whatever
staleTimesays: what the request fetched is fresh for that request, and a refetch could only repeat a request whose result is never shown.revalidateentries refetch invalidated data, so they see a write made earlier in the request. - No retries, even with
retry(), and no garbage collection: the cache lives as long as the request. - No features run. A provider starts them when it mounts, which only happens in the browser.
useIsFetching()returns0, so loading indicators never appear in server HTML.
See SSR and hydration.
Changing the defaults
staleTime, gcTime and structuralSharing can be set for the whole client, or per query in its factory. A query's own option wins:
const const queryClient: QueryClient<{
readonly meta: Flat<Present<RetryMeta>>;
}>
queryClient = createQueryClient<readonly [Link<{
readonly meta: RetryMeta;
}>], []>(config?: QueryClientConfig & {
readonly features?: [] | undefined;
readonly links?: readonly [Link<{
readonly meta: RetryMeta;
}>] | undefined;
}): QueryClient<{
readonly meta: Flat<Present<RetryMeta>>;
}>
Create a query client. Behaviors are opt-in: pass links and features,
or spread recommended(). The client's meta type is what they declare.
createQueryClient({
links?: (readonly AnyLink[] & readonly [Link<{
readonly meta: RetryMeta;
}>]) | undefined
Wrap every request, outermost first.
links: [function retry(options?: RetryOptions): Link<{
readonly meta: RetryMeta;
}>
Retry failed requests. Thrown Responses (router redirects and
not-founds) and cancelled requests aren't retried,
server clients never retry, and a browser that is offline
waits until it's back online before retrying. A query sets its own
attempts with meta: { retry }.
retry({ RetryOptions.attempts?: number | undefinedRetries after the first attempt. A query's meta.retry wins. Default 3.
attempts: 1 })],
QueryClientConfig.staleTime?: number | undefinedDefault staleTime: 0.
staleTime: 30_000,
QueryClientConfig.gcTime?: number | undefinedDefault gcTime: 5 minutes. Server clients never gc.
gcTime: 10 * 60_000,
});| Option | Where | Default |
|---|---|---|
staleTime | Client or query | 0 ms |
gcTime | Client or query | 5 minutes; never on the server |
structuralSharing | Client or query | true |
retry({ attempts, delay, when }) | Client link | 3 attempts, doubling from 1 s up to 30 s, every error but a thrown Response |
meta.retry | Query, with retry() | The link's attempts; false turns retries off |
meta.refetchInterval | Query, with polling() | None |
meta.refetchOnWindowFocus | Query, with focusRefetch() | true |
meta.refetchOnReconnect | Query, with reconnectRefetch() | true |
Watch the defaults at work in Caching, which has a live demo of an entry going from fresh to stale to garbage-collected.