Skip to content

raf-query is early. The API can still change between minor releases, so use it at your own risk.

raf-query

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(): Recommended

The behaviors most apps want: retries, refetching on focus and reconnect, and polling. Spread it into the config:

recommended
(), QueryClientConfig.staleTime?: number | undefined

Default staleTime: 0.

staleTime
: 30_000 });
}
PartKindWhat it does
retry()linkRetries failed requests. See Retries
focusRefetch()featureRefetches read, stale queries when the window regains focus
reconnectRefetch()featureRefetches read, stale queries when the browser comes back online
polling()featureRefetches 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. useQuery reads what's in the cache: a component that mounts with stale data renders it and starts no request, unlike TanStack Query's useQuery, which refetches stale data on mount. Loaders decide freshness: every load entry refetches stale data when a loader starts it (prefetch and swr in the background, revalidate before 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() and reconnectRefetch()
    • on a timer, with polling() and meta.refetchInterval (Polling)

    Rendering the same handle in ten components reads one cache entry.

  • Cached data is stale immediately (staleTime: 0).

  • Polling is per query. meta: { refetchInterval } refetches on a timer while a component reads the query, independent of staleTime. 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 staleTime says: what the request fetched is fresh for that request, and a refetch could only repeat a request whose result is never shown. revalidate entries 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() returns 0, 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 | undefined

Retries after the first attempt. A query's meta.retry wins. Default 3.

attempts
: 1 })],
QueryClientConfig.staleTime?: number | undefined

Default staleTime: 0.

staleTime
: 30_000,
QueryClientConfig.gcTime?: number | undefined

Default gcTime: 5 minutes. Server clients never gc.

gcTime
: 10 * 60_000,
});
OptionWhereDefault
staleTimeClient or query0 ms
gcTimeClient or query5 minutes; never on the server
structuralSharingClient or querytrue
retry({ attempts, delay, when })Client link3 attempts, doubling from 1 s up to 30 s, every error but a thrown Response
meta.retryQuery, with retry()The link's attempts; false turns retries off
meta.refetchIntervalQuery, with polling()None
meta.refetchOnWindowFocusQuery, with focusRefetch()true
meta.refetchOnReconnectQuery, 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.