www/docs/client/react/suspense.md
:::info
<ErrorBoundary />:::
:::tip
useSuspenseQuery & useSuspenseInfiniteQuery both return a [data, query]-tuple, to make it easy to directly use your data and renaming the variable to something descriptive
:::
// @target: esnext
// @filename: server.ts
import { initTRPC, TRPCError } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
const posts = [
{ id: '1', title: 'everlong' },
{ id: '2', title: 'After Dark' },
];
const appRouter = t.router({
post: t.router({
all: t.procedure
.input(
z.object({
cursor: z.string().optional(),
})
)
.query(({ input }) => {
return {
posts,
nextCursor: '123' as string | undefined,
};
}),
byId: t.procedure
.input(
z.object({
id: z.string(),
})
)
.query(({ input }) => {
const post = posts.find(p => p.id === input.id);
if (!post) {
throw new TRPCError({
code: 'NOT_FOUND',
})
}
return post;
}),
}),
});
export type AppRouter = typeof appRouter;
export interface PostPage {
posts: { id: string; title: string }[];
nextCursor?: string | undefined;
}
// @filename: utils/trpc.tsx
// ---cut---
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '../server';
export const trpc = createTRPCReact<AppRouter>();
useSuspenseQuery()// @target: esnext
// @include: server
// @filename: pages/index.tsx
import React from 'react';
// ---cut---
import { trpc } from '../utils/trpc';
function PostView() {
const [post, postQuery] = trpc.post.byId.useSuspenseQuery({ id: '1' });
// ^?
return <></>;
}
useSuspenseInfiniteQuery()// @target: esnext
// @include: server
// @filename: pages/index.tsx
import React from 'react';
// ---cut---
import { trpc } from '../utils/trpc';
import type { PostPage } from '../server';
function PostView() {
const [{ pages }, allPostsQuery] = trpc.post.all.useSuspenseInfiniteQuery(
{},
{
getNextPageParam(lastPage: PostPage) {
return lastPage.nextCursor;
},
initialCursor: '',
},
);
const { isFetching, isFetchingNextPage, fetchNextPage, hasNextPage } =
allPostsQuery;
return <></>;
}
useSuspenseQueries()Suspense equivalent of useQueries().
// @target: esnext
// @include: server
// @filename: pages/index.tsx
import React from 'react';
// ---cut---
import { trpc } from '../utils/trpc';
const Component = (props: { postIds: string[] }) => {
const [posts, postQueries] = trpc.useSuspenseQueries((t) =>
props.postIds.map((id) => t.post.byId({ id })),
);
return <></>;
};
The performance of suspense queries can be improved by prefetching the query data before the Suspense component is rendered (this is sometimes called "render-as-you-fetch").
:::note
:::
// @target: esnext
// @include: server
// @filename: loader.ts
// ---cut---
import { createTRPCQueryUtils } from '@trpc/react-query';
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import { QueryClient } from '@tanstack/react-query';
import type { AppRouter } from './server';
const queryClient = new QueryClient();
const trpcClient = createTRPCClient<AppRouter>({ links: [httpBatchLink({ url: 'http://localhost:3000' })] });
const utils = createTRPCQueryUtils({ queryClient, client: trpcClient });
// tanstack router/ react router loader
const loader = async (params: { id: string }) =>
utils.post.byId.ensureData({ id: params.id });
usePrefetchQuery// @target: esnext
// @include: server
// @filename: pages/index.tsx
// ---cut---
import React, { Suspense } from 'react';
import { trpc } from '../utils/trpc';
function PostView(props: { postId: string }) {
return <></>;
}
function PostViewPage(props: { postId: string }) {
trpc.post.byId.usePrefetchQuery({ id: props.postId });
return (
<Suspense>
<PostView postId={props.postId} />
</Suspense>
);
}
usePrefetchInfiniteQuery// @target: esnext
// @include: server
// @filename: pages/index.tsx
// ---cut---
import React, { Suspense } from 'react';
import { trpc } from '../utils/trpc';
import type { PostPage } from '../server';
function PostView(props: { postId: string }) {
return <></>;
}
function PostViewPage(props: { postId: string }) {
trpc.post.all.usePrefetchInfiniteQuery(
{},
{
getNextPageParam(lastPage: PostPage) {
return lastPage.nextCursor;
},
initialCursor: '',
},
);
return (
<Suspense>
<PostView postId={props.postId} />
</Suspense>
);
}