Guides
Testing
Test components and loaders that use raf-query with a fresh client per test, seeded data, an injectable clock and real browser events.
Components that read queries need a query client and handles. In tests, create both directly. There's nothing to mock in raf-query itself: mock your API, or seed the cache. The examples use Vitest and Testing Library, but nothing depends on them.
A client per test
Create a new client for every test, so no cached data leaks between them:
let let queryClient: QueryClient<any>queryClient: interface QueryClient<TTypes extends RootTypes = any>A query cache. TTypes is its root's types, which factories built with
bindQueryOptions<typeof makeQueryClient>() are checked against. The bare
QueryClient type takes a client of any root.
QueryClient;
function beforeEach(run: () => Promise<void> | void): voidbeforeEach(() => {
let queryClient: QueryClient<any>queryClient = createQueryClient<[], []>(config?: QueryClientConfig & {
readonly features?: [] | undefined;
readonly links?: [] | 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({ QueryClientConfig.isServer?: boolean | undefinedDefault: no window. Server clients never gc and never revalidate cached data.
isServer: false });
});
function afterEach(run: () => Promise<void> | void): voidafterEach(() => {
function removeAll(client: QueryClient): voidRemove every query, aborting their requests.
removeAll(let queryClient: QueryClient<any>queryClient);
});
function function renderWithClient(ui: ReactNode): Promise<RenderResult<typeof import(".pnpm/@testing-library+dom@10.4.2/node_modules/@testing-library/dom/types/queries"), HTMLElement, HTMLElement>>renderWithClient(ui: ReactNodeui: type ReactNode = string | number | bigint | boolean | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<ReactNode> | React.ReactPortal | Promise<AwaitedReactNode> | null | undefinedRepresents all of the things React can render.
ReactNode) {
return act<RenderResult<typeof import(".pnpm/@testing-library+dom@10.4.2/node_modules/@testing-library/dom/types/queries"), HTMLElement, HTMLElement>>(callback: () => RenderResult<typeof import(".pnpm/@testing-library+dom@10.4.2/node_modules/@testing-library/dom/types/queries"), HTMLElement, HTMLElement> | Promise<RenderResult<typeof import(".pnpm/@testing-library+dom@10.4.2/node_modules/@testing-library/dom/types/queries"), HTMLElement, HTMLElement>>): Promise<...> (+1 overload)Wrap any code rendering and triggering updates to your components into act() calls.
act(async () =>
function render(ui: React.ReactNode, options?: Omit<RenderOptions<typeof import(".pnpm/@testing-library+dom@10.4.2/node_modules/@testing-library/dom/types/queries"), HTMLElement, HTMLElement>, "queries"> | undefined): RenderResult (+1 overload)Render into a container which is appended to document.body. It should be used with cleanup.
render(
<function QueryClientProvider(props: {
readonly children?: ReactNode;
readonly client: QueryClient;
}): React.JSX.Element
Provides a client to the tree, and runs its features (focus and
reconnect refetching, polling, …) while mounted.
QueryClientProvider client: QueryClient<any>client={let queryClient: QueryClient<any>queryClient}>
<const Suspense: React.ExoticComponent<React.SuspenseProps>Lets you display a fallback until its children have finished loading.
Suspense React.SuspenseProps.fallback?: ReactNodeA fallback react tree to show when a Suspense child (like React.lazy) suspends
fallback="Loading…">{ui: ReactNodeui}</const Suspense: React.ExoticComponent<React.SuspenseProps>Lets you display a fallback until its children have finished loading.
Suspense>
</function QueryClientProvider(props: {
readonly children?: ReactNode;
readonly client: QueryClient;
}): React.JSX.Element
Provides a client to the tree, and runs its features (focus and
reconnect refetching, polling, …) while mounted.
QueryClientProvider>,
),
);
}
- A client starts with no links and no features: no retries, no refetching on focus, no polling. A failing request fails at once. Add only the parts a test is about. If your app makes its client in a function, let that function take the parts, so tests can leave
retry()out. removeAlldrops every query and aborts requests in flight, so nothing runs after the test ends. Features stop when theQueryClientProviderunmounts, which Testing Library'scleanupdoes after each test.isServer: falsemakes it a browser client even wherewindowisn't defined. See Server rendering.
Render inside an awaited, async act. A tree that suspends inside a sync act, which render on its own uses, is never retried by React once its data lands, with or without a Suspense boundary. The same holds for any use(promise), not only raf-query's.
Testing a component
A component takes a handle as a prop, so a test can start the query itself with load, the same way a loader would:
function test(name: string, run: () => Promise<void> | void): voidtest("shows the post", async () => {
const [const handles: {
readonly post: QueryHandle<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>;
}
handles] = await load<{
readonly post: LoadEntry<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>, "prefetch">;
}>(client: QueryClient, entries: {
readonly post: LoadEntry<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>, "prefetch">;
}): Promise<Loaded<{
readonly post: LoadEntry<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>, "prefetch">;
}>>
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(let queryClient: QueryClient<any>queryClient, {
post: LoadEntry<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>, "prefetch">
post: prefetch<(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>, "prefetch">
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 postQuery(input: {
slug: string;
}): QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>
postQuery, { slug: stringslug: "hello" }),
});
await function renderWithClient(ui: ReactNode): Promise<RenderResult<typeof import(".pnpm/@testing-library+dom@10.4.2/node_modules/@testing-library/dom/types/queries"), HTMLElement, HTMLElement>>renderWithClient(<function Post(props: {
handle: QueryHandle<typeof postQuery>;
}): ReactNode
Post handle: QueryHandle<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>
handle={const handles: {
readonly post: QueryHandle<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>;
}
handles.post: QueryHandle<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>
post} />);
function expect(actual: unknown): {
toBe(expected: unknown): void;
toBeDefined(): void;
toBeUndefined(): void;
}
expect((await const screen: Screen<typeof import(".pnpm/@testing-library+dom@10.4.2/node_modules/@testing-library/dom/types/queries")>screen.findByRole<HTMLElement>(role: ByRoleMatcher, options?: ByRoleOptions | undefined, waitForElementOptions?: waitForOptions | undefined): Promise<HTMLElement> (+1 overload)findByRole("heading")).Element.textContent: stringtextContent).function toBe(expected: unknown): voidtoBe("Hello");
});
Seeding the cache
To render with known data and no request at all, write it with setData before loading:
const const queryClient: QueryClient<{
readonly meta: Flat<unknown>;
}>
queryClient = createQueryClient<[], []>(config?: QueryClientConfig & {
readonly features?: [] | undefined;
readonly links?: [] | 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({
QueryClientConfig.isServer?: boolean | undefinedDefault: no window. Server clients never gc and never revalidate cached data.
isServer: false,
QueryClientConfig.staleTime?: number | undefinedDefault staleTime: 0.
staleTime: var Number: NumberConstructorAn object that represents a number of any kind. All JavaScript numbers are 64-bit floating-point numbers.
Number.NumberConstructor.POSITIVE_INFINITY: numberA value greater than the largest number that can be represented in JavaScript.
JavaScript displays POSITIVE_INFINITY values as infinity.
POSITIVE_INFINITY,
});
setData<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>(client: ClientFor<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>, factory: (input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>, input: {
slug: string;
}, updater: Updater<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>): void (+1 overload)
Write a query's data, as if a request had just returned it. updater
gets the current data, or undefined if there is none. A factory that
takes no input (or an optional one) skips the input:
setData(client, todosQuery, updater).
setData(
const queryClient: QueryClient<{
readonly meta: Flat<unknown>;
}>
queryClient,
function postQuery(input: {
slug: string;
}): QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>
postQuery,
{ slug: stringslug: "hello" },
{ Post.slug: stringslug: "hello", Post.title: stringtitle: "Hello", Post.body: stringbody: "", Post.authorId: stringauthorId: "" },
);
const [const handles: {
readonly post: QueryHandle<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>;
}
handles] = await load<{
readonly post: LoadEntry<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>, "swr">;
}>(client: QueryClient, entries: {
readonly post: LoadEntry<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>, "swr">;
}): Promise<Loaded<{
readonly post: LoadEntry<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>, "swr">;
}>>
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, {
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: "hello" }),
});
swr resolves at once when the cache has data. It also refetches stale data in the background, and data written with setData is fresh only for staleTime, which is 0 by default: give the test client a staleTime so the seeded data stays as it is.
Mocking the network
The queryFn is your code, so mock what it calls. With fetch, MSW intercepts requests without changing application code:
const server: {
use(...handlers: ReadonlyArray<unknown>): void;
}
server.function use(...handlers: ReadonlyArray<unknown>): voiduse(
const http: {
get(path: string, resolver: () => unknown): unknown;
}
http.function get(path: string, resolver: () => unknown): unknownget("/api/posts/:slug", () =>
const HttpResponse: {
json(body: unknown): unknown;
}
HttpResponse.function json(body: unknown): unknownjson({ slug: stringslug: "hello", title: stringtitle: "Hello" }),
),
);
Testing loaders
A loader is a function of its context. Export it from the route file, call it with a client, and check what it started:
function test(name: string, run: () => Promise<void> | void): voidtest("the post loader starts both queries", async () => {
const const queryClient: QueryClient<{
readonly meta: Flat<unknown>;
}>
queryClient = createQueryClient<[], []>(config?: QueryClientConfig & {
readonly features?: [] | undefined;
readonly links?: [] | 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({ QueryClientConfig.isServer?: boolean | undefinedDefault: no window. Server clients never gc and never revalidate cached data.
isServer: false });
await function postLoader(args: {
context: {
queryClient: QueryClient;
};
params: {
slug: string;
};
}): Promise<{
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>>;
}>
postLoader({
context: {
queryClient: QueryClient;
}
context: { queryClient: QueryClient<any>queryClient },
params: {
slug: string;
}
params: { slug: stringslug: "hello" },
});
function expect(actual: unknown): {
toBe(expected: unknown): void;
toBeDefined(): void;
toBeUndefined(): void;
}
expect(getData<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>(client: ClientFor<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>, factory: (input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>, input: {
slug: string;
}): Post | undefined
A query's cached data (before select), or undefined.
getData(const queryClient: QueryClient<{
readonly meta: Flat<unknown>;
}>
queryClient, function postQuery(input: {
slug: string;
}): QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>
postQuery, { slug: stringslug: "hello" })).function toBeDefined(): voidtoBeDefined();
function expect(actual: unknown): {
toBe(expected: unknown): void;
toBeDefined(): void;
toBeUndefined(): void;
}
expect(function isFetching(client: QueryClient, target?: AnyInvalidation): numberHow many queries have a request in flight, optionally only a factory's, a group's or a target's.
isFetching(const queryClient: QueryClient<{
readonly meta: Flat<unknown>;
}>
queryClient, function commentsQuery(input: {
slug: string;
}): InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, DefaultTypes>
commentsQuery)).function toBe(expected: unknown): voidtoBe(1);
});getData reads what an swr or revalidate entry loaded, and isFetching(queryClient, target) counts the requests still in flight, such as a prefetch's.
Time
Staleness, garbage collection, retry delays, timeouts and polling depend on time. The client reads it from its clock: now(), setTimeout and clearTimeout. Pass your own to createQueryClient({ clock }), and a test of gcTime or staleTime runs in milliseconds, without touching globals:
interface Timer {
readonly Timer.at: numberat: number;
readonly Timer.callback: () => voidcallback: () => void;
}
/** A clock whose timers fire only when the test advances it. */
function function fakeClock(): Clock & {
advance(ms: number): void;
}
A clock whose timers fire only when the test advances it.
fakeClock(): Clock & { function advance(ms: number): voidadvance(ms: numberms: number): void } {
let let now: numbernow = 0;
let let timers: Timer[]timers: interface Array<T>Array<Timer> = [];
return {
Clock.now(): numbernow: () => let now: numbernow,
Clock.setTimeout(callback: () => void, ms: number): unknownsetTimeout(callback: () => voidcallback, ms: numberms) {
const const timer: Timertimer: Timer = { Timer.at: numberat: let now: numbernow + ms: numberms, Timer.callback: () => voidcallback };
let timers: Timer[]timers.Array<Timer>.push(...items: Timer[]): numberAppends new elements to the end of an array, and returns the new length of the array.
push(const timer: Timertimer);
return const timer: Timertimer;
},
Clock.clearTimeout(handle: unknown): voidclearTimeout(handle: unknownhandle) {
let timers: Timer[]timers = let timers: Timer[]timers.Array<Timer>.filter(predicate: (value: Timer, index: number, array: Timer[]) => unknown, thisArg?: any): Timer[] (+1 overload)Returns the elements of an array that meet the condition specified in a callback function.
filter((timer: Timertimer) => timer: Timertimer !== handle: unknownhandle);
},
function advance(ms: number): voidadvance(ms: numberms) {
const const end: numberend = let now: numbernow + ms: numberms;
for (;;) {
const const due: Timerdue = let timers: Timer[]timers
.Array<Timer>.filter(predicate: (value: Timer, index: number, array: Timer[]) => unknown, thisArg?: any): Timer[] (+1 overload)Returns the elements of an array that meet the condition specified in a callback function.
filter((timer: Timertimer) => timer: Timertimer.Timer.at: numberat <= const end: numberend)
.Array<Timer>.toSorted(compareFn?: ((a: Timer, b: Timer) => number) | undefined): Timer[]Returns a copy of an array with its elements sorted.
toSorted((a: Timera, b: Timerb) => a: Timera.Timer.at: numberat - b: Timerb.Timer.at: numberat)[0];
if (!const due: Timerdue) break;
let timers: Timer[]timers = let timers: Timer[]timers.Array<Timer>.filter(predicate: (value: Timer, index: number, array: Timer[]) => unknown, thisArg?: any): Timer[] (+1 overload)Returns the elements of an array that meet the condition specified in a callback function.
filter((timer: Timertimer) => timer: Timertimer !== const due: Timerdue);
let now: numbernow = const due: Timerdue.Timer.at: numberat;
const due: Timerdue.Timer.callback: () => voidcallback();
}
let now: numbernow = const end: numberend;
},
};
}
function test(name: string, run: () => Promise<void> | void): voidtest("garbage-collects unread entries", () => {
const const clock: Clock & {
advance(ms: number): void;
}
clock = function fakeClock(): Clock & {
advance(ms: number): void;
}
A clock whose timers fire only when the test advances it.
fakeClock();
const const queryClient: QueryClient<{
readonly meta: Flat<unknown>;
}>
queryClient = createQueryClient<[], []>(config?: QueryClientConfig & {
readonly features?: [] | undefined;
readonly links?: [] | 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({ QueryClientConfig.clock?: Clock | undefinedclock, QueryClientConfig.isServer?: boolean | undefinedDefault: no window. Server clients never gc and never revalidate cached data.
isServer: false });
const const post: Postpost: Post = {
Post.slug: stringslug: "hello",
Post.title: stringtitle: "Hello",
Post.body: stringbody: "",
Post.authorId: stringauthorId: "ada",
};
setData<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>(client: ClientFor<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>, factory: (input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>, input: {
slug: string;
}, updater: Updater<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>): void (+1 overload)
Write a query's data, as if a request had just returned it. updater
gets the current data, or undefined if there is none. A factory that
takes no input (or an optional one) skips the input:
setData(client, todosQuery, updater).
setData(const queryClient: QueryClient<{
readonly meta: Flat<unknown>;
}>
queryClient, function postQuery(input: {
slug: string;
}): QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>
postQuery, { slug: stringslug: "hello" }, const post: Postpost);
const clock: Clock & {
advance(ms: number): void;
}
clock.function advance(ms: number): voidadvance(5 * 60 * 1000); // the default gcTime
function expect(actual: unknown): {
toBe(expected: unknown): void;
toBeDefined(): void;
toBeUndefined(): void;
}
expect(getData<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>(client: ClientFor<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>, factory: (input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>, input: {
slug: string;
}): Post | undefined
A query's cached data (before select), or undefined.
getData(const queryClient: QueryClient<{
readonly meta: Flat<unknown>;
}>
queryClient, function postQuery(input: {
slug: string;
}): QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>
postQuery, { slug: stringslug: "hello" })).function toBeUndefined(): voidtoBeUndefined();
});The default clock calls Date.now() and setTimeout each time, so vi.useFakeTimers() works as well.
Focus and the network
focusRefetch() and reconnectRefetch() listen to the page's own events, so a test in a DOM environment fires them. Give the client the feature, render under a QueryClientProvider (features only run while one has the client mounted), and dispatch the event:
function test(name: string, run: () => Promise<void> | void): voidtest("refetches on focus", async () => {
let let calls: numbercalls = 0;
const const countQuery: () => QueryOptions<readonly ["count"], number, number, DefaultTypes>countQuery = () =>
queryOptions<readonly ["count"], number, number>(options: BaseOptions<readonly ["count"], DefaultTypes> & {
readonly queryFn: (context: QueryFunctionContext<readonly ["count"]>) => Awaitable<number>;
readonly select?: ((data: number) => number) | undefined;
}): QueryOptions<readonly ["count"], number, number, DefaultTypes> (+1 overload)
Typed options for a query factory:
queryOptions({ BaseOptions<readonly ["count"], DefaultTypes>.queryKey: readonly ["count"]A plain array, usually built by a group(). Handles keep its literal type.
queryKey: const count: Group
<readonly []>() => readonly ["count"]
count(), queryFn: (context: QueryFunctionContext<readonly ["count"]>) => Awaitable<number>queryFn: async () => ++let calls: numbercalls });
function function (local function) Count(props: {
handle: QueryHandle<() => QueryOptions<readonly ["count"], number, number, DefaultTypes>>;
}): React.JSX.Element
Count(props: {
handle: QueryHandle<() => QueryOptions<readonly ["count"], number, number, DefaultTypes>>;
}
props: { handle: QueryHandle<() => QueryOptions<readonly ["count"], number, number, DefaultTypes>>handle: interface QueryHandle<TFactory extends QueryFactory = (input: unknown) => { readonly queryFn: (context: never) => unknown; readonly queryKey: QueryKey; }>A small serializable reference to a query: its key and its factory's
input. It carries no data; readers get that from the cache. Handles of
different factories have different types.
QueryHandle<typeof const countQuery: () => QueryOptions<readonly ["count"], number, number, DefaultTypes>countQuery> }) {
const [const countThenable: QueryValue<() => QueryOptions<readonly ["count"], number, number, DefaultTypes>>countThenable] = useQuery<() => QueryOptions<readonly ["count"], number, number, DefaultTypes>>(factory: () => QueryOptions<readonly ["count"], number, number, DefaultTypes>, handle: QueryHandle<() => QueryOptions<readonly ["count"], number, number, DefaultTypes>>, options?: UseQueryOptions): readonly [QueryValue<() => QueryOptions<readonly ["count"], number, number, DefaultTypes>>, QueryState<() => QueryOptions<readonly ["count"], number, number, DefaultTypes>>]Read a query by its handle.
useQuery(const countQuery: () => QueryOptions<readonly ["count"], number, number, DefaultTypes>countQuery, props: {
handle: QueryHandle<() => QueryOptions<readonly ["count"], number, number, DefaultTypes>>;
}
props.handle: QueryHandle<() => QueryOptions<readonly ["count"], number, number, DefaultTypes>>handle);
const const count: numbercount = use<number>(usable: React.Usable<number>): numberuse(const countThenable: QueryValue<() => QueryOptions<readonly ["count"], number, number, DefaultTypes>>countThenable);
return <React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>{const count: numbercount}</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>;
}
let queryClient: QueryClient<any>queryClient = createQueryClient<[], readonly [Feature<{
readonly meta: FocusRefetchMeta;
}>]>(config?: QueryClientConfig & {
readonly features?: readonly [Feature<{
readonly meta: FocusRefetchMeta;
}>] | undefined;
readonly links?: [] | undefined;
}): QueryClient<{
readonly meta: Flat<Present<FocusRefetchMeta>>;
}>
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({
QueryClientConfig.isServer?: boolean | undefinedDefault: no window. Server clients never gc and never revalidate cached data.
isServer: false,
features?: (readonly AnyFeature[] & readonly [Feature<{
readonly meta: FocusRefetchMeta;
}>]) | undefined
Behaviors started while a provider has the client mounted.
features: [function focusRefetch(): Feature<{
readonly meta: FocusRefetchMeta;
}>
Refetch read, stale queries when the window regains focus, except those
with meta: { refetchOnWindowFocus: false }.
focusRefetch()],
});
const [const handles: {
readonly count: QueryHandle<() => QueryOptions<readonly ["count"], number, number, DefaultTypes>>;
}
handles] = await load<{
readonly count: LoadEntry<() => QueryOptions<readonly ["count"], number, number, DefaultTypes>, "revalidate">;
}>(client: QueryClient, entries: {
readonly count: LoadEntry<() => QueryOptions<readonly ["count"], number, number, DefaultTypes>, "revalidate">;
}): Promise<Loaded<{
readonly count: LoadEntry<() => QueryOptions<readonly ["count"], number, number, DefaultTypes>, "revalidate">;
}>>
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(let queryClient: QueryClient<any>queryClient, {
count: LoadEntry<() => QueryOptions<readonly ["count"], number, number, DefaultTypes>, "revalidate">count: revalidate<() => QueryOptions<readonly ["count"], number, number, DefaultTypes>>(factory: () => QueryOptions<readonly ["count"], number, number, DefaultTypes>, ...args: LoadEntryArgs<() => QueryOptions<readonly ["count"], number, number, DefaultTypes>>): LoadEntry<() => QueryOptions<readonly ["count"], number, number, DefaultTypes>, "revalidate">Wait for fresh data, as HTTP's
must-revalidate
does. Fresh cached data resolves at once without a refetch (staleTime
is the max-age); with stale data or none it waits for the fetch. On a
server client, data counts as fresh until a write invalidates it. The
load rejects if the fetch fails.
revalidate(const countQuery: () => QueryOptions<readonly ["count"], number, number, DefaultTypes>countQuery),
});
await function renderWithClient(ui: ReactNode): Promise<RenderResult<typeof import(".pnpm/@testing-library+dom@10.4.2/node_modules/@testing-library/dom/types/queries"), HTMLElement, HTMLElement>>renderWithClient(<function (local function) Count(props: {
handle: QueryHandle<() => QueryOptions<readonly ["count"], number, number, DefaultTypes>>;
}): React.JSX.Element
Count handle: QueryHandle<() => QueryOptions<readonly ["count"], number, number, DefaultTypes>>handle={const handles: {
readonly count: QueryHandle<() => QueryOptions<readonly ["count"], number, number, DefaultTypes>>;
}
handles.count: QueryHandle<() => QueryOptions<readonly ["count"], number, number, DefaultTypes>>count} />);
await act<void>(callback: () => void | Promise<void>): Promise<void> (+1 overload)Wrap any code rendering and triggering updates to your components into act() calls.
act(async () => {
var window: Window & typeof globalThisThe window property of a Window object points to the window object itself.
window.function dispatchEvent(event: Event): booleanThe dispatchEvent() method of the EventTarget sends an Event to the object, (synchronously) invoking the affected event listeners in the appropriate order. The normal event processing rules (including the capturing and optional bubbling phase) also apply to events dispatched manually with dispatchEvent().
dispatchEvent(new var Event: new (type: string, eventInitDict?: EventInit) => EventThe Event interface represents an event which takes place on an EventTarget.
Event("focus"));
});
function expect(actual: unknown): {
toBe(expected: unknown): void;
toBeDefined(): void;
toBeUndefined(): void;
}
expect(let calls: numbercalls).function toBe(expected: unknown): voidtoBe(2);
});focusRefetch() refetches on focus only while document.visibilityState is "visible". For reconnecting, dispatch new Event("online"). Retries wait for that event too, while navigator.onLine is false.
Server rendering
A client created where window isn't defined is a server client: no retries, no garbage collection, and cached data is never revalidated. To test browser behavior in Node without a DOM environment, pass isServer: false:
const const queryClient: QueryClient<{
readonly meta: Flat<unknown>;
}>
queryClient = createQueryClient<[], []>(config?: QueryClientConfig & {
readonly features?: [] | undefined;
readonly links?: [] | 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({ QueryClientConfig.isServer?: boolean | undefinedDefault: no window. Server clients never gc and never revalidate cached data.
isServer: false });