Skip to content

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

raf-query

Reference

API reference

Every export of @raf-query/react, its TanStack Router subpath and @raf-query/core, with query options and their defaults.

raf-query's API is functions, not methods: the client is a cache you pass to load and to the verbs. @raf-query/react re-exports everything from @raf-query/core, so apps import from one place.

The client

const 
const queryClient: QueryClient<{
    readonly meta: Flat<unknown>;
}>
queryClient
=
createQueryClient<readonly AnyLink[], readonly AnyFeature[]>(config?: QueryClientConfig & {
    readonly features?: readonly AnyFeature[] | undefined;
    readonly links?: readonly AnyLink[] | undefined;
}): QueryClient<{
    readonly meta: Flat<unknown>;
}>

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
(const config: QueryClientConfig | undefinedconfig);
ExportUse
createQueryClient(config?)Make a query cache. config: links, features, staleTime, gcTime, structuralSharing, isServer, clock. Important defaults
recommended(){ links: [retry()], features: [focusRefetch(), reconnectRefetch(), polling()] }, to spread into the config
QueryClient<TTypes>The client type. Bare QueryClient takes a client of any root; verbs take ClientFor<typeof factory>. Query meta
client.subscribe(listener, { signal }?)Cache events: added, updated, removed, and readers (a reader subscribed or left)
client.isServerSet from config.isServer, else from whether window exists. Server clients don't retry, never gc, and reuse cached data. SSR and hydration
link(run), Link, OperationWrap every request, outermost first. link<{ meta: … }>(run) declares the meta it reads. Query meta
feature(setup?), FeatureA cache-wide behavior, started while a provider mounts the client. feature<{ meta: … }>() without setup only declares meta fields
ClockThe type of config.clock: time and timers. Pass a fake one in tests. Testing

Links and features:

ExportKindUse
retry({ attempts, delay, when }?)linkRetry failed requests: 3 attempts, doubling from 1 s up to 30 s. meta.retry per query. Retries
timeout(ms)linkFail a request with QueryTimeoutError, and abort its signal, when it takes longer than ms
focusRefetch()featureRefetch read, stale queries on window focus; meta.refetchOnWindowFocus: false opts out. Window focus refetching
reconnectRefetch()featureRefetch read, stale queries when the browser comes back online; meta.refetchOnReconnect: false opts out
polling()featureRefetch read queries every meta.refetchInterval ms. Polling

Query factories

ExportUse
queryOptions(options)Typed options for a query factory. Query factories
infiniteQueryOptions(options)Typed options for a paged query factory. Infinite queries
group(name), group(parent, name), GroupBuild flat keys under a path; a group is also a target that covers every key under its path. Query keys
bindQueryOptions<typeof makeQueryClient>(), bindInfiniteQueryOptions<…>()The builder bound to a root: checks meta against the root's links and features. Query meta
DataOf<TFactory>, InputOf<TFactory>A factory's data and input types
QueryOptions, QueryFunctionContext, StreamOptionsOption types. The context has queryKey, signal and meta
InfiniteQueryOptions, InfiniteQueryFunctionContext, InfiniteDataPaged option types. The context adds pageParam and direction; the data is { pages, pageParams }
QueryFactory, QueryKeyA function of an input that returns query options, and a key: any plain array
QueryMeta, QueryOptionsBuilder, InfiniteQueryOptionsBuilderA query's meta when its builder isn't bound to a root, and the types of the bound builders

Query options, for queryOptions and infiniteQueryOptions:

OptionDefault
queryKeyRequired. Query keys
queryFnRequired. Query functions
staleTimeThe client's, else 0 ms
gcTimeThe client's, else 5 minutes; never on the server
structuralSharing (keep deeply equal parts of data)The client's, else true
select (derive what readers get from the data)None
stream ({ initial, reduce }, for a queryFn that returns an async iterable)None. Query functions
meta (settings for the root's links and features)None

infiniteQueryOptions also takes initialPageParam, getNextPageParam, getPreviousPageParam and maxPages.

Starting queries

const [
const handles: {
    readonly comments: QueryHandle<(input: {
        slug: string;
    }) => InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, DefaultTypes>>;
    readonly post: QueryHandle<(input: {
        slug: string;
    }) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>;
}
handles
,
const data: LoadedData<{
    readonly comments: LoadEntry<(input: {
        slug: string;
    }) => InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, DefaultTypes>, "prefetch">;
    readonly post: LoadEntry<(input: {
        slug: string;
    }) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>, "swr">;
}>
data
] = await
load<{
    readonly comments: LoadEntry<(input: {
        slug: string;
    }) => InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, DefaultTypes>, "prefetch">;
    readonly post: LoadEntry<(input: {
        slug: string;
    }) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>, "swr">;
}>(client: QueryClient, entries: {
    readonly comments: LoadEntry<(input: {
        slug: string;
    }) => InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, DefaultTypes>, "prefetch">;
    readonly post: LoadEntry<(input: {
        slug: string;
    }) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>, "swr">;
}): Promise<...>

Start a loader's queries and return a serializable handle for each. It starts every entry before it waits for any, so the order of the entries can't create a waterfall. Resolves with [handles, data] once every swr and revalidate entry has data, and rejects with the error as soon as one fails. prefetch entries never make it reject. A skipToken in place of an entry starts nothing and gets no handle.

load
(
const queryClient: QueryClient<{
    readonly meta: Flat<unknown>;
}>
queryClient
, {
comments: LoadEntry<(input: {
    slug: string;
}) => InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, DefaultTypes>, "prefetch">
comments
:
prefetch<(input: {
    slug: string;
}) => InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, DefaultTypes>>(factory: (input: {
    slug: string;
}) => InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, DefaultTypes>, ...args: LoadEntryArgs<(input: {
    slug: string;
}) => InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, DefaultTypes>>): LoadEntry<...>

Start the query if the cache has no fresh data, and don't wait for it. It never makes the load reject: errors show up where a component reads the handle.

prefetch
(
function commentsQuery(input: {
    slug: string;
}): InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, DefaultTypes>
commentsQuery
, { slug: stringslug }),
post: LoadEntry<(input: {
    slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>, "swr">
post
:
swr<(input: {
    slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>(factory: (input: {
    slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>, ...args: LoadEntryArgs<(input: {
    slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>): LoadEntry<(input: {
    slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>, "swr">

Use cached data even when it's stale, and wait only when the cache has none. Stale data resolves at once and refetches in the background, as HTTP's stale-while-revalidate does. The load rejects if the fetch it waits for fails.

swr
(
function postQuery(input: {
    slug: string;
}): QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>
postQuery
, { slug: stringslug }),
});
ExportUse
load(client, entries)Start every entry, then resolve with [handles, data]. Rejects when an swr or revalidate entry fails. Starting queries
prefetch(factory, input, options?)Start the query if the cache has no fresh data; never waits
swr(factory, input, options?)Wait only when the cache has no data; stale data resolves at once and refetches
revalidate(factory, input, options?)Wait for fresh data; fresh cached data resolves without a refetch
skipTokenIn place of an entry: start nothing. Its handle is undefined and data has no key for it. Conditional queries
waterfallHandle(factory, input)The handle of a query whose input is only known while rendering. Request waterfalls
waterfallHandle(factory, handle, update)A handle with update(handle.input) as its input, or handle itself while that maps to the same query. URL state
QueryHandle<TFactory>{ queryKey, input }, serializable; what load returns for every entry but a skipToken. Handles
LoadEntry, Loaded, LoadEntryOptionsEntry and result types. LoadEntryOptions is { pages }: how many pages a paged factory's first load fetches

Reading queries

const [
const postThenable: QueryValue<(input: {
    slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>
postThenable
,
const postState: QueryState<(input: {
    slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>
postState
] =
useQuery<(input: {
    slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>(factory: (input: {
    slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>, handle: QueryHandle<(input: {
    slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>, options?: UseQueryOptions): readonly [QueryValue<(input: {
    slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>, QueryState<...>]

Read a query by its handle.

useQuery
(
const factory: (input: {
    slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>
factory
,
const handle: QueryHandle<(input: {
    slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>
handle
, const options: UseQueryOptions | undefinedoptions);
const const post: Postpost = use<Post>(usable: React.Usable<Post>): Postuse(
const postThenable: QueryValue<(input: {
    slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>
postThenable
);

useQuery(factory, handle, options?) returns a Thenable<Data> for every handle, next to its state. Read it with use(), which returns at once when the data is cached and suspends otherwise. A query nothing loaded fetches while rendering in the browser; on the server it renders its Suspense fallback. It takes factories that aren't paged. The hooks belong in client components: in a server component each throws. Reading queries

Options:

OptionUse
whilePending"suspend" (the default) shows the Suspense fallback for a new input; "previous" keeps the last data
ControlMeaning
isPendingNo data yet for the handle's input; use() on the thenable will suspend
isPreviousShowing the previous handle's data while the new one loads (whilePending: "previous")
isFetchingA request is in flight: first load, new input, or background refetch
isStaleData is older than staleTime, or invalidated
errorThe last request's error while it's the latest outcome. Existing data stays readable
updatedAtWhen the data was fetched; 0 without data
queryKey, inputThe handle's key and input
hashStable string form of the handle's key, for key props on Suspense boundaries
refetch()Fetch again, or join the request in flight. Returns a promise that resolves when it settles

The state is tracked. The component re-renders when the thenable changes or settles, or when a field it read while rendering changes. The state object keeps its identity until then, and its functions keep theirs while the component is mounted. A field read only in an effect or a handler isn't tracked and holds its value from the last render. Tracked state

useInfiniteQuery(factory, handle, options?) reads a paged factory the same way, with the same options. Its state adds fetchNextPage(), fetchPreviousPage(), hasNextPage, hasPreviousPage, isFetchingNextPage and isFetchingPreviousPage, and it's tracked the same way: a component that reads only the pages doesn't re-render while another page loads, and one that reads isFetchingNextPage does. Infinite queries

Read the thenable's status, value and reason to render without Suspense. See Reading without Suspense.

ExportUse
useIsFetching(target?)How many queries have a request in flight: all, or a factory's, group's or target's. 0 on the server. Background fetching indicators
QueryState, InfiniteQueryState, PageControls, QueryValue, UseQueryOptions, ThenableTypes of what useQuery and useInfiniteQuery take and return

Changing the cache

Each verb takes the client first. A factory matches the queries it built: all of them, or those whose input matches a partial input (PartialInput<TFactory>, part of the factory's input, whatever its key looks like). A group matches the keys that start with its path. A target ({ where }, such as a tag) matches the queries its where picks. Query invalidation

ExportUse
invalidateOne(client, factory, input)Mark one query stale. If it's read, it refetches; resolves when that settles
invalidateMany(client, factory, partial?) / (client, group) / (client, target, param?)Mark many queries stale; read ones refetch
invalidateMany(client, [target, [factory, partial], [target, param], …])Several targets in one call: each read query they cover refetches once, side by side; resolves when all settle
invalidateAll(client)Mark every query stale; read ones refetch
resetOne, resetMany, resetAllDrop data and errors. Read queries suspend and refetch; resolves when they settle. resetMany takes a list too
removeOne, removeMany, removeAllRemove entries and abort their requests. removeMany takes a list too
setData(client, factory, input, updater) / (client, factory, updater)Write data, as if a request had just returned it. A factory without an input takes the second form. Mutations
getData(client, factory, input)Cached data (before select), or undefined
isFetching(client, target?)How many queries have a request in flight
ClientFor<TFactory>, PartialInput<TFactory>, Target, QueryInfo, GroupTypes the verbs take. QueryInfo is what where sees: queryKey, input, data, meta

Errors

ExportUse
QueryTimeoutErrorWhat a request fails with when timeout() cuts it off
abortError()Makes the AbortError (a DOMException) that a load waiting on a removed query rejects with

Providers

ExportUse
QueryClientProviderProvides a client to the tree, and runs its features while mounted. In every framework; see the provider
useQueryClient()Read the client
bindUseQueryClient<typeof makeQueryClient>()useQueryClient, typed with a root, so verbs check factories against it. Query meta
HydrationBoundaryWrites server data into the client: a loader's state, or in server components the request's client. React Router

Hydration and devtools

Also on the main entry. An app that doesn't import them doesn't bundle them.

ExportUse
dehydrate, dehydrateStream, hydrate, canDehydrate, RedactedError, and their option and state typesMove a cache to the browser without an integration. SSR and hydration
QueryDevtools, QueryDevtoolsPanelDevtools

@raf-query/core exports the same, except the devtools, for code that doesn't use React.

TanStack Router

EntryExports
@raf-query/react/tanstack-routersetupRouterIntegration({ queryClient, router, dehydrateOptions?, handleRedirects? }). TanStack Start