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, ascontext.meta wheretargets, asquery.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>>>> | undefinedSettings 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 },
});
}
| Field | Read by | Use |
|---|---|---|
retry | retry() | Attempts for this query, or false. See Retries |
refetchInterval | polling() | Refetch every n ms while read. See Polling |
refetchOnWindowFocus | focusRefetch() | false opts out. See Window focus refetching |
refetchOnReconnect | reconnectRefetch() | 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.
Links and features
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
opand callsnext(op)to run the rest of the chain. The last link is the query's ownqueryFn. Links run outermost first.retry()andtimeout(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()andpolling()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(): RecommendedThe 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(): RecommendedThe 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:
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,
},
});
}
metatakes 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 },});
- Verbs such as
invalidateManyandsetDatatake only that root's clients. The bounduseQueryClientreturns a client of the root, so verbs called in components check their factories too. The plainuseQueryClient()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>
);
}wheretargets see the root'smeta, 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",});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.