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

Links, features and meta

Set per-query options for the client's links and features with meta, declare your own fields, and type meta by the client it belongs to.

meta holds a query's settings for the client's links and features, and anything else you want to know about the query elsewhere. It reaches:

  • every link, as op.meta
  • the queryFn, as context.meta
  • where targets, as query.meta, to select queries by it

The per-query behavior that recommended() turns on is set here, and each field is read by the part that does it:

import { const queryOptions: QueryOptionsBuilder<DefaultTypes>

Typed options for a query factory:

queryOptions
} from "@raf-query/react";
export function
function postsQuery(input: {
    page: number;
    sort: "newest" | "oldest";
}): QueryOptions<readonly ["posts", "list", {
    page: number;
    sort: "newest" | "oldest";
}], readonly Post[], readonly Post[], DefaultTypes>
postsQuery
(
input: {
    page: number;
    sort: "newest" | "oldest";
}
input
: { page: numberpage: number; sort: "newest" | "oldest"sort: "newest" | "oldest" }) {
return
queryOptions<readonly ["posts", "list", {
    page: number;
    sort: "newest" | "oldest";
}], readonly Post[], readonly Post[]>(options: BaseOptions<readonly ["posts", "list", {
    page: number;
    sort: "newest" | "oldest";
}], DefaultTypes> & {
    readonly queryFn: (context: QueryFunctionContext<readonly ["posts", "list", {
        page: number;
        sort: "newest" | "oldest";
    }]>) => Awaitable<readonly Post[]>;
    readonly select?: ((data: readonly Post[]) => readonly Post[]) | undefined;
}): QueryOptions<readonly ["posts", "list", {
    page: number;
    sort: "newest" | "oldest";
}], readonly Post[], readonly Post[], DefaultTypes> (+1 overload)

Typed options for a query factory:

queryOptions
({
BaseOptions<readonly ["posts", "list", { page: number; sort: "newest" | "oldest"; }], DefaultTypes>.queryKey: readonly ["posts", "list", {
    page: number;
    sort: "newest" | "oldest";
}]

A plain array, usually built by a group(). Handles keep its literal type.

queryKey
:
const posts: Group
<readonly ["list", {
    page: number;
    sort: "newest" | "oldest";
}]>(rest_0: "list", rest_1: {
    page: number;
    sort: "newest" | "oldest";
}) => readonly ["posts", "list", {
    page: number;
    sort: "newest" | "oldest";
}]
posts
("list",
input: {
    page: number;
    sort: "newest" | "oldest";
}
input
),
queryFn: (context: QueryFunctionContext<readonly ["posts", "list", {
    page: number;
    sort: "newest" | "oldest";
}]>) => Awaitable<readonly Post[]>
queryFn
: () =>
function listPosts(input: {
    page: number;
    sort: "newest" | "oldest";
}): Promise<readonly Post[]>
listPosts
(
input: {
    page: number;
    sort: "newest" | "oldest";
}
input
),
// Read by `retry()` and `focusRefetch()`, which `recommended()` includes. BaseOptions<readonly ["posts", "list", { page: number; sort: "newest" | "oldest"; }], DefaultTypes>.meta?: NoInfer<Partial<Readonly<Record<string, unknown>>>> | undefined

Settings for the root's links and features, and data for targets: the fields the root's parts declare, such as retry with the retry() link.

meta
: { retry: numberretry: 1, refetchOnWindowFocus: booleanrefetchOnWindowFocus: false },
}); }
FieldRead byUse
retryretry()Attempts for this query, or false. See Retries
refetchIntervalpolling()Refetch every n ms while read. See Polling
refetchOnWindowFocusfocusRefetch()false opts out. See Window focus refetching
refetchOnReconnectreconnectRefetch()false opts out

A field does something only when the client has the part that reads it: meta: { retry: 1 } on a client without retry() is just data.

A client's behavior comes from two kinds of part, both passed to createQueryClient:

  • A link wraps every request, like a tRPC link: it gets the request as op and calls next(op) to run the rest of the chain. The last link is the query's own queryFn. Links run outermost first. retry() and timeout(ms) are links.
  • A feature is a cache-wide behavior. It starts when a provider mounts the client and stops when the last provider of that client unmounts, so it runs once however many providers share the client. focusRefetch(), reconnectRefetch() and polling() are features.

Each part declares the meta fields it reads, with link<{ meta: … }>(fn) or feature<{ meta: … }>(setup). A feature without setup only declares fields: an app's own data, for targets to match on. Here a link toasts a per-query message when a request fails, and a feature declares an owner:

import {
	
function createQueryClient<const TLinks extends ReadonlyArray<AnyLink> = [], const TFeatures extends ReadonlyArray<AnyFeature> = []>(config?: QueryClientConfig & {
    readonly features?: TFeatures;
    readonly links?: TLinks;
}): QueryClient<{
    readonly meta: MetaOf<[...TLinks, ...TFeatures]>;
}>

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 feature<TDecl extends Decl = Decl>(setup?: FeatureFunction): Feature<TDecl>

A feature that declares the meta fields it reads. Without setup, it only declares them: a root's own fields, such as an owner its targets match on.

feature
,
function link<TDecl extends Decl = Decl>(run: (op: Operation<MetaOfDecl<TDecl> & object>, next: (op: Operation) => Promise<unknown>) => Promise<unknown>): Link<TDecl>

A link that declares the meta fields it reads. A root's queries take those fields only when the root has the link.

link
,
function recommended(): Recommended

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

recommended
,
} from "@raf-query/react"; // A link that reads `meta.errorMessage`: one toast per failed request. const
const errorToasts: Link<{
    meta: {
        errorMessage?: string;
    };
}>
errorToasts
=
link<{
    meta: {
        errorMessage?: string;
    };
}>(run: (op: Operation<Present<{
    errorMessage?: string;
}> & object>, next: (op: Operation) => Promise<unknown>) => Promise<unknown>): Link<{
    meta: {
        errorMessage?: string;
    };
}>

A link that declares the meta fields it reads. A root's queries take those fields only when the root has the link.

link
<{
meta: {
    errorMessage?: string;
}
meta
: { errorMessage?: string | undefinederrorMessage?: string } }>(
async (
op: Operation<Present<{
    errorMessage?: string;
}> & object>
op
, next: (op: Operation) => Promise<unknown>next) => {
try { return await next: (op: Operation) => Promise<unknown>next(
op: Operation<Present<{
    errorMessage?: string;
}> & object>
op
);
} catch (function (local var) error: unknownerror) {
const toast: {
    error(message: string): void;
}
toast
.function error(message: string): voiderror(
op: Operation<Present<{
    errorMessage?: string;
}> & object>
op
.
Operation<Present<{ errorMessage?: string; }> & object>.meta: Present<{
    errorMessage?: string;
}> & object

The query's meta, {} when it has none.

meta
.errorMessage: string | undefinederrorMessage ?? "Something went wrong");
throw function (local var) error: unknownerror; } }, ); // A feature with no behavior: it only declares a field, for targets to match on. const
const ownership: Feature<{
    meta: {
        owner?: "editorial" | "community";
    };
}>
ownership
=
feature<{
    meta: {
        owner?: "editorial" | "community";
    };
}>(setup?: FeatureFunction): Feature<{
    meta: {
        owner?: "editorial" | "community";
    };
}>

A feature that declares the meta fields it reads. Without setup, it only declares them: a root's own fields, such as an owner its targets match on.

feature
<{
meta: {
    owner?: "editorial" | "community";
}
meta
: { owner?: "editorial" | "community" | undefinedowner?: "editorial" | "community" } }>();
export function
function makeQueryClient(): QueryClient<{
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<{
        owner?: "editorial" | "community";
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta>>;
}>
makeQueryClient
() {
const {
const features: readonly [Feature<{
    readonly meta: FocusRefetchMeta;
}>, Feature<{
    readonly meta: ReconnectRefetchMeta;
}>, Feature<{
    readonly meta: PollingMeta;
}>]
features
,
const links: readonly [Link<{
    readonly meta: RetryMeta;
}>]
links
} = function recommended(): Recommended

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

recommended
();
return
createQueryClient<readonly [Link<{
    meta: {
        errorMessage?: string;
    };
}>, Link<{
    readonly meta: RetryMeta;
}>], readonly [Feature<{
    readonly meta: FocusRefetchMeta;
}>, Feature<{
    readonly meta: ReconnectRefetchMeta;
}>, Feature<{
    readonly meta: PollingMeta;
}>, Feature<{
    meta: {
        owner?: "editorial" | "community";
    };
}>]>(config?: QueryClientConfig & {
    readonly features?: readonly [Feature<{
        readonly meta: FocusRefetchMeta;
    }>, Feature<...>, Feature<...>, 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
({
links?: (readonly AnyLink[] & readonly [Link<{
    meta: {
        errorMessage?: string;
    };
}>, Link<{
    readonly meta: RetryMeta;
}>]) | undefined

Wrap every request, outermost first.

links
: [
const errorToasts: Link<{
    meta: {
        errorMessage?: string;
    };
}>
errorToasts
, ...
const links: readonly [Link<{
    readonly meta: RetryMeta;
}>]
links
],
features?: (readonly AnyFeature[] & readonly [Feature<{
    readonly meta: FocusRefetchMeta;
}>, Feature<{
    readonly meta: ReconnectRefetchMeta;
}>, Feature<{
    readonly meta: PollingMeta;
}>, Feature<{
    meta: {
        owner?: "editorial" | "community";
    };
}>]) | undefined

Behaviors started while a provider has the client mounted.

features
: [...
const features: readonly [Feature<{
    readonly meta: FocusRefetchMeta;
}>, Feature<{
    readonly meta: ReconnectRefetchMeta;
}>, Feature<{
    readonly meta: PollingMeta;
}>]
features
,
const ownership: Feature<{
    meta: {
        owner?: "editorial" | "community";
    };
}>
ownership
],
}); }

A package that ships a link or feature needs only these exports: link and feature, the types Link, Feature and Operation (the request a link gets) to name them, and Target and QueryInfo for a target that matches on the fields it declares. Tags are built the same way, in a few lines of app code.

errorToasts comes before retry(), so it sees a request's error only after its retries ran out.

Typing meta by its root

A client's root is the links and features it's made with, and the root's meta is the fields they declare. The plain queryOptions takes any meta. To check meta against a root, bind the builders to it once per app, in a file of their own, and import them from there instead of from @raf-query/react:

src/raf.ts
import {
	function bindInfiniteQueryOptions<T>(): InfiniteQueryOptionsBuilder<RootOf<T>>

infiniteQueryOptions bound to a root, like bindQueryOptions. Bind it next to queryOptions, in apps that page:

bindInfiniteQueryOptions
,
function bindQueryOptions<T>(): QueryOptionsBuilder<RootOf<T>>

queryOptions bound to a root: meta takes the fields the root's links and features declare, and verbs take only that root's clients. T is the client's type, or the type of a function that makes it. Bind once per app:

bindQueryOptions
,
function bindUseQueryClient<T>(): () => QueryClient<RootOf<T>>

useQueryClient typed with a root, so verbs called with the client it returns check factories and targets against that root. T is the client's type, or the type of a function that makes it. Bind once per app:

bindUseQueryClient
,
} from "@raf-query/react"; // `makeQueryClient` is the function above, imported with `import type`. type
type Client = () => QueryClient<{
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}>
Client
= typeof
function makeQueryClient(): QueryClient<{
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}>
makeQueryClient
;
export const
const queryOptions: QueryOptionsBuilder<{
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}>
queryOptions
=
bindQueryOptions<() => QueryClient<{
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}>>(): QueryOptionsBuilder<{
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}>

queryOptions bound to a root: meta takes the fields the root's links and features declare, and verbs take only that root's clients. T is the client's type, or the type of a function that makes it. Bind once per app:

bindQueryOptions
<
type Client = () => QueryClient<{
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}>
Client
>();
export const
const useQueryClient: () => QueryClient<{
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}>
useQueryClient
=
bindUseQueryClient<() => QueryClient<{
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}>>(): () => QueryClient<{
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}>

useQueryClient typed with a root, so verbs called with the client it returns check factories and targets against that root. T is the client's type, or the type of a function that makes it. Bind once per app:

bindUseQueryClient
<
type Client = () => QueryClient<{
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}>
Client
>();
// Only in an app that pages: export const
const infiniteQueryOptions: InfiniteQueryOptionsBuilder<{
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}>
infiniteQueryOptions
=
bindInfiniteQueryOptions<() => QueryClient<{
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}>>(): InfiniteQueryOptionsBuilder<{
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}>

infiniteQueryOptions bound to a root, like bindQueryOptions. Bind it next to queryOptions, in apps that page:

bindInfiniteQueryOptions
<
type Client = () => QueryClient<{
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}>
Client
>();

Each binder takes the type of the function that makes the client, or of the client. Only the type is read, so import type is enough and the factories don't import the client. A binder returns the plain builder, typed for the root: binding costs a few bytes, and an app that doesn't page leaves out bindInfiniteQueryOptions and ships none of the paging code.

Factories built with the bound queryOptions name no root themselves:

export function 
function commentsQuery(input: {
    slug: string;
}): InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, {
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}>
commentsQuery
(
input: {
    slug: string;
}
input
: { slug: stringslug: string }) {
return
const infiniteQueryOptions: InfiniteQueryOptionsBuilder
<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>>(options: InfiniteOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, {
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<...>>;
}> & {
    ...;
}) => InfiniteQueryOptions<...>
infiniteQueryOptions
({
BaseOptions<readonly ["posts", "comments", string], { readonly meta: Flat<Present<{ errorMessage?: string; }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<...> & Present<...> & Present<...>>; }>.queryKey: readonly ["posts", "comments", string]

A plain array, usually built by a group(). Handles keep its literal type.

queryKey
: comments<readonly [string]>(rest_0: string): readonly ["posts", "comments", string]comments(
input: {
    slug: string;
}
input
.slug: stringslug),
queryFn: (context: InfiniteQueryFunctionContext<readonly ["posts", "comments", string], number>) => CommentPage | PromiseLike<CommentPage>queryFn: (context: InfiniteQueryFunctionContext<readonly ["posts", "comments", string], number>context) =>
function listComments(input: {
    cursor: number;
    slug: string;
}): Promise<CommentPage>
listComments
({ cursor: numbercursor: context: InfiniteQueryFunctionContext<readonly ["posts", "comments", string], number>context.InfiniteQueryFunctionContext<readonly ["posts", "comments", string], number>.pageParam: numberpageParam, slug: stringslug:
input: {
    slug: string;
}
input
.slug: stringslug }),
InfiniteOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, { readonly meta: Flat<Present<{ errorMessage?: string; }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<...> & Present<...> & Present<...>>; }>.initialPageParam: numberinitialPageParam: 0, InfiniteOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, { readonly meta: Flat<Present<{ errorMessage?: string; }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<...> & Present<...> & Present<...>>; }>.getNextPageParam: PageParamFn<CommentPage, number>getNextPageParam: (lastPage: CommentPagelastPage) => lastPage: CommentPagelastPage.CommentPage.nextCursor: number | nullnextCursor,
BaseOptions<readonly ["posts", "comments", string], { readonly meta: Flat<Present<{ errorMessage?: string; }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<...> & Present<...> & Present<...>>; }>.meta?: NoInfer<Partial<Flat<Present<{
    errorMessage?: string;
}> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
    owner?: "editorial" | "community";
}>>>> | undefined

Settings for the root's links and features, and data for targets: the fields the root's parts declare, such as retry with the retry() link.

meta
: {
errorMessage?: string | undefinederrorMessage: "Couldn't load the comments", owner?: "editorial" | "community" | undefinedowner: "community", retry?: number | false | undefinedretry: 1, }, }); }
  • meta takes only the fields the root's parts declare, with their types:
const queryOptions: QueryOptionsBuilder
<readonly ["posts", "bySlug", "hello-world"], unknown, unknown>(options: BaseOptions<readonly ["posts", "bySlug", "hello-world"], {
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}> & {
    readonly queryFn: (context: QueryFunctionContext<readonly ["posts", "bySlug", "hello-world"]>) => AsyncIterable<...>;
    readonly stream: StreamOptions<...>;
}) => QueryOptions<...> (+1 overload)
queryOptions
({
BaseOptions<readonly ["posts", "bySlug", "hello-world"], { readonly meta: Flat<Present<{ errorMessage?: string; }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<...> & Present<...> & Present<...>>; }>.queryKey: readonly ["posts", "bySlug", "hello-world"]

A plain array, usually built by a group(). Handles keep its literal type.

queryKey
: posts<readonly ["bySlug", "hello-world"]>(rest_0: "bySlug", rest_1: "hello-world"): readonly ["posts", "bySlug", "hello-world"]posts("bySlug", "hello-world"),
queryFn: (context: QueryFunctionContext<readonly ["posts", "bySlug", "hello-world"]>) => AsyncIterable<unknown>queryFn: () => function getPost(slug: string): Promise<Post>getPost("hello-world"),
BaseOptions<readonly ["posts", "bySlug", "hello-world"], { readonly meta: Flat<Present<{ errorMessage?: string; }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<...> & Present<...> & Present<...>>; }>.meta?: NoInfer<Partial<Flat<Present<{
    errorMessage?: string;
}> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
    owner?: "editorial" | "community";
}>>>> | undefined

Settings for the root's links and features, and data for targets: the fields the root's parts declare, such as retry with the retry() link.

meta
: { cacheFor: 60_000 },
No overload matches this call. Overload 2 of 2, '(options: BaseOptions<readonly ["posts", "bySlug", "hello-world"], { readonly meta: Flat<Present<{ errorMessage?: string | undefined; }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<...> & Present<...> & Present<...>>; }> & { ...; }): QueryOptions<...>', gave the following error. Object literal may only specify known properties, and 'cacheFor' does not exist in type 'Partial<Flat<Present<{ errorMessage?: string | undefined; }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<...> & Present<...>>>'.
});
  • Verbs such as invalidateMany and setData take only that root's clients. The bound useQueryClient returns a client of the root, so verbs called in components check their factories too. The plain useQueryClient() returns a client of any root, which takes any factory.
import { function invalidateMany<TFactory extends QueryFactory>(client: ClientFor<TFactory>, factory: TFactory, partial?: PartialInput<TFactory>): Promise<void> (+3 overloads)

Mark queries stale: a factory's (optionally only those whose input matches partial), a group's (keys that start with its path, whether the group built them or not), or those a target's where picks. Read queries refetch now; the rest when next read. Resolves when the refetches settle.

invalidateMany
} from "@raf-query/react";
export function
function RefreshComments(props: {
    slug: string;
}): React.JSX.Element
RefreshComments
(
props: {
    slug: string;
}
props
: { slug: stringslug: string }) {
const
const client: QueryClient<{
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}>
client
=
const useQueryClient: () => QueryClient<{
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}>
useQueryClient
();
return ( <React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefinedonClick={() =>
invalidateMany<(input: {
    slug: string;
}) => InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, {
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}>>(client: ClientFor<...>, factory: (input: {
    slug: string;
}) => InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, {
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}>, partial?: {
    ...;
} | undefined): Promise<void> (+3 overloads)

Mark queries stale: a factory's (optionally only those whose input matches partial), a group's (keys that start with its path, whether the group built them or not), or those a target's where picks. Read queries refetch now; the rest when next read. Resolves when the refetches settle.

invalidateMany
(
const client: QueryClient<{
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}>
client
,
function commentsQuery(input: {
    slug: string;
}): InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, {
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}>
commentsQuery
,
props: {
    slug: string;
}
props
)}
React.ButtonHTMLAttributes<HTMLButtonElement>.type?: "button" | "submit" | "reset" | undefinedtype="button" > Refresh </React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button> ); }
  • where targets see the root's meta, as the next section shows.

The root is a function, not a client at module level, because a server makes one client per request. It's the same idea as a tRPC v12 client typing its call options from its links.

Selecting queries by meta

A where target picks queries by what their meta says. On a client of a typed root, query.meta has the root's fields:

import { function invalidateMany<TFactory extends QueryFactory>(client: ClientFor<TFactory>, factory: TFactory, partial?: PartialInput<TFactory>): Promise<void> (+3 overloads)

Mark queries stale: a factory's (optionally only those whose input matches partial), a group's (keys that start with its path, whether the group built them or not), or those a target's where picks. Read queries refetch now; the rest when next read. Resolves when the refetches settle.

invalidateMany
} from "@raf-query/react";
const
const qc: QueryClient<{
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}>
qc
=
function makeQueryClient(): QueryClient<{
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}>
makeQueryClient
();
await
invalidateMany<{
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}, never>(client: QueryClient<{
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}>, target: Target<...>): Promise<void> (+3 overloads)

Mark queries stale: a factory's (optionally only those whose input matches partial), a group's (keys that start with its path, whether the group built them or not), or those a target's where picks. Read queries refetch now; the rest when next read. Resolves when the refetches settle.

invalidateMany
(
const qc: QueryClient<{
    readonly meta: Flat<Present<{
        errorMessage?: string;
    }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
        owner?: "editorial" | "community";
    }>>;
}>
qc
, {
Target<NoInfer<Flat<Present<{ errorMessage?: string; }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<...> & Present<...>>>, never>.where: (query: QueryInfo<NoInfer<Flat<Present<{
    errorMessage?: string;
}> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
    owner?: "editorial" | "community";
}>>>>, param: never) => boolean
where
: (
query: QueryInfo<NoInfer<Flat<Present<{
    errorMessage?: string;
}> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
    owner?: "editorial" | "community";
}>>>>
query
) =>
query: QueryInfo<NoInfer<Flat<Present<{
    errorMessage?: string;
}> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
    owner?: "editorial" | "community";
}>>>>
query
.meta.owner: "editorial" | "community" | undefinedowner === "community",
QueryInfo<NoInfer<Flat<Present<{ errorMessage?: string; }> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<...> & Present<...>>>>.meta: Flat<Present<{
    errorMessage?: string;
}> & Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<{
    owner?: "editorial" | "community";
}>>

The meta its factory gave it. An entry only hydrated has {} until it's read.

});

See Query invalidation for targets, and tags, which are targets built on meta.

Serialization

meta isn't sent with dehydrated state during SSR: it comes from the factory, so it can hold anything, functions included. An entry only hydrated, which nothing has read through its factory yet, has {} as its meta. Invalidations still reach it: they're checked again when the entry is next read.