Set up with TanStack Start
On this page
This guide shows how to use tRPC with TanStack Start: route loaders start queries, and components read them through handles.
tRPC adds no router integration of its own. Data crosses from the server to the browser through TanStack's SSR query integration, with your tRPC data transformer.
The SSE chat example is a complete TanStack Start app that uses handles in its loaders.
1. Install deps
npm install @trpc/server @trpc/client @trpc/tanstack-react-query @tanstack/react-query @tanstack/react-router-ssr-query superjson zod2. Create a query client factory
Use your tRPC transformer to serialize query data, and set a staleTime above 0 so the browser doesn't refetch right after hydration:
import { class QueryClientQueryClient } from '@tanstack/react-query';
import class superjsonsuperjson from 'superjson';
export function function createQueryClient(): QueryClientcreateQueryClient() {
return new new QueryClient(config?: QueryClientConfig): QueryClientQueryClient({
QueryClientConfig.defaultOptions?: DefaultOptions<Error> | undefineddefaultOptions: {
DefaultOptions<Error>.queries?: OmitKeyof<QueryObserverOptions<unknown, Error, unknown, unknown, readonly unknown[], never>, "suspense" | "queryKey", "strictly"> | undefinedqueries: { staleTime?: StaleTimeFunction<unknown, Error, unknown, readonly unknown[]> | undefinedstaleTime: 30 * 1000 },
DefaultOptions<Error>.dehydrate?: DehydrateOptions | undefineddehydrate: { DehydrateOptions.serializeData?: TransformerFn | undefinedserializeData: class superjsonsuperjson.SuperJSON.serialize: (object: SuperJSONValue) => SuperJSONResultserialize },
DefaultOptions<Error>.hydrate?: {
deserializeData?: TransformerFn;
queries?: QueryOptions;
mutations?: MutationOptions<unknown, DefaultError, unknown, unknown>;
} | undefined
hydrate: { deserializeData?: TransformerFn | undefineddeserializeData: class superjsonsuperjson.SuperJSON.deserialize: <T = unknown>(payload: SuperJSONResult, options?: {
inPlace?: boolean;
}) => T
deserialize },
},
});
}3. Create an isomorphic tRPC client
During server rendering, call procedures in the same process with localLink. In the browser, call them over HTTP:
import { function createIsomorphicFn(): IsomorphicFnBasecreateIsomorphicFn } from '@tanstack/react-start';
import { function getRequest(): RequestgetRequest } from '@tanstack/react-start/server';
import {
function createTRPCClient<TRouter extends AnyRouter>(opts: CreateTRPCClientOptions<TRouter>): TRPCClient<TRouter>createTRPCClient,
function httpBatchStreamLink<TRouter extends AnyRouter>(opts: HTTPBatchStreamLinkOptions<TRouter["_def"]["_config"]["$types"]>): TRPCLink<TRouter>httpBatchStreamLink,
function localLink<TRouter extends AnyRouter>(opts: LocalLinkOptions<TRouter>): TRPCLink<TRouter>localLink,
type type TRPCLink<TInferrable extends InferrableClientTypes> = (opts: TRPCClientRuntime) => OperationLink<TInferrable>TRPCLink,
} from '@trpc/client';
import { function createTRPCContext<TRouter extends AnyRouter, TFeatureFlags extends FeatureFlags = {
keyPrefix: false;
}>(): CreateTRPCContextResult<TRouter, TFeatureFlags>
createTRPCContext, type type TRPCOptionsProxy<TRouter extends AnyTRPCRouter, TFeatureFlags extends FeatureFlags = { keyPrefix: false; }> = DecoratedRouterRecord<TRouter["_def"]["_config"]["$types"], TRouter["_def"]["record"], TFeatureFlags> & DecorateRouterKeyable<TFeatureFlags>TRPCOptionsProxy } from '@trpc/tanstack-react-query';
import class superjsonsuperjson from 'superjson';
import { function createContext(opts: {
req: Request;
}): Promise<{
req: Request;
}>
createContext } from '~/server/context';
import { const appRouter: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>
appRouter, type type AppRouter = Router<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>> & DecorateCreateRouterOptions<...>
AppRouter } from '~/server/routers/_app';
export const { const TRPCProvider: TRPCProviderType<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
TRPCProvider, const useTRPC: () => TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
useTRPC } = createTRPCContext<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>(): CreateTRPCContextResult<...>
createTRPCContext<type AppRouter = Router<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>> & DecorateCreateRouterOptions<...>
AppRouter>();
export type type TRPC = DecoratedRouterRecord<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>, {
...;
}> & DecorateRouterKeyable<...>
TRPC = type TRPCOptionsProxy<TRouter extends AnyTRPCRouter, TFeatureFlags extends FeatureFlags = { keyPrefix: false; }> = DecoratedRouterRecord<TRouter["_def"]["_config"]["$types"], TRouter["_def"]["record"], TFeatureFlags> & DecorateRouterKeyable<TFeatureFlags>TRPCOptionsProxy<type AppRouter = Router<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>> & DecorateCreateRouterOptions<...>
AppRouter>;
const const getLinks: IsomorphicFn<[], TRPCLink<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>[], TRPCLink<...>[]>
getLinks = function createIsomorphicFn(): IsomorphicFnBasecreateIsomorphicFn()
.IsomorphicFnBase.server: <[], TRPCLink<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>[]>(serverImpl: () => TRPCLink<...>[]) => ServerOnlyFn<...>
server((): type TRPCLink<TInferrable extends InferrableClientTypes> = (opts: TRPCClientRuntime) => OperationLink<TInferrable>TRPCLink<type AppRouter = Router<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>> & DecorateCreateRouterOptions<...>
AppRouter>[] => [
localLink<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>(opts: LocalLinkOptions<...>): TRPCLink<...>
localLink({
router: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>
router: const appRouter: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>
appRouter,
createContext: () => Promise<{
req: Request;
}>
createContext: () => function createContext(opts: {
req: Request;
}): Promise<{
req: Request;
}>
createContext({ req: Requestreq: function getRequest(): RequestgetRequest() }),
transformer: DataTransformerOptionstransformer: class superjsonsuperjson,
}),
])
.ServerOnlyFn<[], TRPCLink<BuiltRouter<{ ctx: { req: Request; }; meta: object; errorShape: DefaultErrorShape; transformer: true; }, DecorateCreateRouterOptions<{ post: BuiltRouter<{ ctx: { req: Request; }; meta: object; errorShape: DefaultErrorShape; transformer: true; }, DecorateCreateRouterOptions<...>>; }>>>[]>.client: <TRPCLink<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>[]>(clientImpl: () => TRPCLink<...>[]) => IsomorphicFn<...>
client((): type TRPCLink<TInferrable extends InferrableClientTypes> = (opts: TRPCClientRuntime) => OperationLink<TInferrable>TRPCLink<type AppRouter = Router<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>> & DecorateCreateRouterOptions<...>
AppRouter>[] => [
httpBatchStreamLink<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>(opts: HTTPBatchStreamLinkOptions<...>): TRPCLink<...>
httpBatchStreamLink({ url: string | URLurl: '/api/trpc', transformer: DataTransformerOptionstransformer: class superjsonsuperjson }),
]);
export const const makeTRPCClient: () => TRPCClient<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>
makeTRPCClient = () =>
createTRPCClient<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>(opts: CreateTRPCClientOptions<...>): TRPCClient<...>
createTRPCClient<type AppRouter = Router<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>> & DecorateCreateRouterOptions<...>
AppRouter>({ links: TRPCLink<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>[]
links: const getLinks: () => TRPCLink<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>[]
getLinks() });The browser's link calls /api/trpc. Serve it from a server route with the fetch adapter:
import { function createFileRoute<TFilePath extends keyof FileRoutesByPath, TParentRoute extends AnyRoute = FileRoutesByPath[TFilePath]["parentRoute"], TId extends RouteConstraints["TId"] = FileRoutesByPath[TFilePath]["id"], TPath extends RouteConstraints["TPath"] = FileRoutesByPath[TFilePath]["path"], TFullPath extends RouteConstraints["TFullPath"] = FileRoutesByPath[TFilePath]["fullPath"]>(path?: TFilePath): FileRoute<TFilePath, TParentRoute, TId, TPath, TFullPath>["createRoute"]createFileRoute } from '@tanstack/react-router';
import { function fetchRequestHandler<TRouter extends AnyRouter>(opts: FetchHandlerRequestOptions<TRouter>): Promise<Response>fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { function createContext(opts: {
req: Request;
}): Promise<{
req: Request;
}>
createContext } from '~/server/context';
import { const appRouter: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>
appRouter } from '~/server/routers/_app';
const const handler: ({ request }: {
request: Request;
}) => Promise<Response>
handler = ({ request: Requestrequest }: { request: Requestrequest: Request }) =>
fetchRequestHandler<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>(opts: FetchHandlerRequestOptions<...>): Promise<Response>
fetchRequestHandler({
endpoint: stringendpoint: '/api/trpc',
req: Requestreq: request: Requestrequest,
BaseHandlerOptions<BuiltRouter<{ ctx: { req: Request; }; meta: object; errorShape: DefaultErrorShape; transformer: true; }, DecorateCreateRouterOptions<{ post: BuiltRouter<{ ctx: { req: Request; }; meta: object; errorShape: DefaultErrorShape; transformer: true; }, DecorateCreateRouterOptions<...>>; }>>, Request>.router: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>
router: const appRouter: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>
appRouter,
createContext: FetchCreateContextFn<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>
createContext,
});
export const const Route: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/api/trpc/$", "/api/trpc/$", "/api/trpc/$", "/api/trpc/$", undefined, ResolveParams<"/api/trpc/$">, AnyContext, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, {
...;
}>
Route = createFileRoute<"/api/trpc/$", RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/api/trpc/$", "/api/trpc/$", "/api/trpc/$">(path?: "/api/trpc/$" | undefined): <TRegister, TSearchValidator, TParams, TRouteContextFn, TBeforeLoadFn, TLoaderDeps, TLoaderFn, TChildren, TSSR, TMiddlewares, THandlers>(options?: (ParamsOptions<...> & ... 1 more ... & UpdatableRouteOptions<...>) | undefined) => Route<...>
createFileRoute('/api/trpc/$')({
FilebaseRouteOptionsInterface<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 12 more ..., { ...; }>.server?: RouteServerOptions<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/api/trpc/$", ResolveParams<"/api/trpc/$">, {}, undefined, AnyContext, AnyContext, AnyContext, unknown, {
...;
}> | undefined
server: { RouteServerOptions<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 8 more ..., { ...; }>.handlers?: Constrain<{
...;
}, Partial<...> | ((opts: HandlersFnOpts<...>) => CustomHandlerFunctionsRecord<...>)> | undefined
handlers: { type GET: ({ request }: {
request: Request;
}) => Promise<Response>
GET: const handler: ({ request }: {
request: Request;
}) => Promise<Response>
handler, type POST: ({ request }: {
request: Request;
}) => Promise<Response>
POST: const handler: ({ request }: {
request: Request;
}) => Promise<Response>
handler } },
});4. Put the proxy in the router context
Create one proxy per router with createTRPCOptionsProxy, and pass it to loaders through the router context:
import { const createRouter: CreateRouterFncreateRouter } from '@tanstack/react-router';
import { function setupRouterSsrQueryIntegration<TRouter extends AnyRouter>(opts: Options<TRouter>): voidsetupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query';
import { function createTRPCOptionsProxy<TRouter extends AnyRouter, TFeatureFlags extends FeatureFlags = {
keyPrefix: false;
}>(opts: TRPCOptionsProxyOptions<TRouter, TFeatureFlags>): TRPCOptionsProxy<TRouter, TFeatureFlags>
createTRPCOptionsProxy } from '@trpc/tanstack-react-query';
import { function createQueryClient(): QueryClientcreateQueryClient } from '~/lib/query-client';
import { const makeTRPCClient: () => TRPCClient<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>
makeTRPCClient, const TRPCProvider: TRPCProviderType<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
TRPCProvider } from '~/lib/trpc';
import type { type AppRouter = Router<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>> & DecorateCreateRouterOptions<...>
AppRouter } from '~/server/routers/_app';
import { const routeTree: Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, {}, unknown, unknown, unknown, undefined>
routeTree } from './routeTree.gen';
export function function getRouter(): RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, {}, unknown, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>
getRouter() {
const const queryClient: QueryClientqueryClient = function createQueryClient(): QueryClientcreateQueryClient();
const const trpcClient: TRPCClient<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>
trpcClient = function makeTRPCClient(): TRPCClient<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>
makeTRPCClient();
const const trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
trpc = createTRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>(opts: TRPCOptionsProxyOptions<...>): TRPCOptionsProxy<...>
createTRPCOptionsProxy<type AppRouter = Router<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>> & DecorateCreateRouterOptions<...>
AppRouter>({
TRPCOptionsProxyOptionsExternal<BuiltRouter<{ ctx: { req: Request; }; meta: object; errorShape: DefaultErrorShape; transformer: true; }, DecorateCreateRouterOptions<{ post: BuiltRouter<{ ctx: { req: Request; }; meta: object; errorShape: DefaultErrorShape; transformer: true; }, DecorateCreateRouterOptions<...>>; }>>>.client: TRPCClient<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>> | TRPCUntypedClient<...>
client: const trpcClient: TRPCClient<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>
trpcClient,
queryClient: QueryClient | (() => QueryClient)queryClient,
});
const const router: RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, {}, unknown, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>
router = createRouter<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, {}, unknown, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>(options: RouterConstructorOptions<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, ... 8 more ..., undefined>, "never", false, RouterHistory, Record<...>>): RouterCore<...>
createRouter({
routeTree?: Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, {}, unknown, unknown, unknown, undefined> | undefined
routeTree,
context: {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}
context: { queryClient: QueryClientqueryClient, trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>
trpc },
defaultPreload?: false | "intent" | "viewport" | "render" | undefineddefaultPreload: 'intent',
// Let the query cache decide what's fresh: loaders always run, and fresh data is reused
defaultPreloadStaleTime?: number | undefineddefaultPreloadStaleTime: 0,
Wrap?: ((props: {
children: any;
}) => React.JSX.Element) | undefined
Wrap: (props: {
children: any;
}
props) => (
<const TRPCProvider: TRPCProviderType<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
TRPCProvider trpcClient: TRPCClient<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>
trpcClient={const trpcClient: TRPCClient<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>
trpcClient} queryClient: QueryClientqueryClient={const queryClient: QueryClientqueryClient}>
{props: {
children: any;
}
props.children: anychildren}
</const TRPCProvider: TRPCProviderType<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
TRPCProvider>
),
});
setupRouterSsrQueryIntegration<RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, {}, unknown, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>>(opts: Options<RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, ... 8 more ..., undefined>, "never", false, RouterHistory, Record<...>>>): void
setupRouterSsrQueryIntegration({ router: RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, {}, unknown, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>
router, queryClient: QueryClientqueryClient });
return const router: RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, {}, unknown, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>
router;
}Type the root route's context with createRootRouteWithContext<{ queryClient: QueryClient; trpc: TRPCOptionsProxy<AppRouter> }>().
5. Load handles in loaders
A loader lists its queries as entries, loads them with loadHandles, and returns the handles as loader data. For each entry, choose whether the loader waits for it. Loading handles compares the options. Every input should come from the route's params or search params: see Start every query from the URL.
- On the first visit, the loader runs on the server, and the data is sent with the page.
- On later navigations, the loader runs in the browser against the tab's cache. Fresh data is reused; stale data renders at once and refetches.
The page shows at once, with skeletons until the data comes in. Stale data shows at once and refetches.
Comments
No requests yet.
Without waiting: prefetch entries
The load resolves at once, and the route renders before the data arrives:
import { function createFileRoute<TFilePath extends keyof FileRoutesByPath, TParentRoute extends AnyRoute = FileRoutesByPath[TFilePath]["parentRoute"], TId extends RouteConstraints["TId"] = FileRoutesByPath[TFilePath]["id"], TPath extends RouteConstraints["TPath"] = FileRoutesByPath[TFilePath]["path"], TFullPath extends RouteConstraints["TFullPath"] = FileRoutesByPath[TFilePath]["fullPath"]>(path?: TFilePath): FileRoute<TFilePath, TParentRoute, TId, TPath, TFullPath>["createRoute"]createFileRoute } from '@tanstack/react-router';
import { function loadHandles<TFeatureFlags extends FeatureFlags, TEntries extends Record<string, AnyTRPCEntry>>(trpc: DecorateRouterKeyable<TFeatureFlags>, entries: TEntries): Promise<TRPCLoadedHandles<TEntries>>loadHandles, function prefetchInfiniteEntry<TDef extends ResolverDef>(procedure: DecorateInfiniteQueryProcedure<TDef>, input: TDef["input"], opts?: TRPCInfiniteEntryOptions<TDef>): TRPCInfiniteQueryEntry<TDef, "prefetch">prefetchInfiniteEntry } from '@trpc/tanstack-react-query/unstable-handles';
import { const Suspense: React.ExoticComponent<React.SuspenseProps>Suspense } from 'react';
import { function Comments(props: {
comments: inferInfiniteQueryHandle<TRPC["post"]["comments"]>;
}): JSX.Element
Comments } from '~/components/comments';
export const const Route: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts/$id/comments", "/posts/$id/comments", "/posts/$id/comments", "/posts/$id/comments", undefined, ResolveParams<"/posts/$id/comments">, AnyContext, AnyContext, AnyContext, ... 6 more ..., undefined>
Route = createFileRoute<"/posts/$id/comments", RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts/$id/comments", "/posts/$id/comments", "/posts/$id/comments">(path?: "/posts/$id/comments" | undefined): <TRegister, TSearchValidator, TParams, TRouteContextFn, TBeforeLoadFn, TLoaderDeps, TLoaderFn, TChildren, TSSR, TMiddlewares, THandlers>(options?: (ParamsOptions<...> & ... 1 more ... & UpdatableRouteOptions<...>) | undefined) => Route<...>
createFileRoute('/posts/$id/comments')({
FilebaseRouteOptionsInterface<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 12 more ..., undefined>.loader?: Constrain<({ context: { trpc }, params }: LoaderFnContext<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts/$id/comments", ResolveParams<"/posts/$id/comments">, {}, AnyContext, AnyContext, AnyContext, unknown, undefined>) => Promise<...>, RouteLoaderFn<...> | RouteLoaderObject<...>> | undefined
loader: async ({ LoaderFnContext<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 7 more ..., undefined>.context: {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}
context: { trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>
trpc }, params: {
id: string;
}
params }) => {
const [const handles: HandlesOf<{
comments: TRPCInfiniteQueryEntry<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "prefetch">;
}>
handles] = await loadHandles<FeatureFlags, {
comments: TRPCInfiniteQueryEntry<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "prefetch">;
}>(trpc: DecorateRouterKeyable<FeatureFlags>, entries: {
comments: TRPCInfiniteQueryEntry<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "prefetch">;
}): Promise<...>
loadHandles(trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>
trpc, {
comments: TRPCInfiniteQueryEntry<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "prefetch">
comments: prefetchInfiniteEntry<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>(procedure: DecorateInfiniteQueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>, input: {
...;
}, opts?: TRPCInfiniteEntryOptions<...> | undefined): TRPCInfiniteQueryEntry<...>
prefetchInfiniteEntry(
trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>
trpc.post: DecoratedRouterRecord<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>, {
...;
}> & DecorateRouterKeyable<...>
post.comments: DecorateQueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}> & DecorateInfiniteQueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
comments,
{ postId: stringpostId: params: {
id: string;
}
params.id: stringid, limit?: number | undefinedlimit: 20 },
{ getNextPageParam: (last: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
}) => number | null
getNextPageParam: (last: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
}
last) => last: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
}
last.nextCursor: number | nullnextCursor },
),
});
return const handles: HandlesOf<{
comments: TRPCInfiniteQueryEntry<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "prefetch">;
}>
handles;
},
UpdatableRouteOptionsExtensions.component?: RouteComponent | undefinedcomponent: function CommentsPage(): JSX.ElementCommentsPage,
});
function function CommentsPage(): JSX.ElementCommentsPage() {
const const loaderData: {
comments: TRPCInfiniteQueryHandle<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>;
}
loaderData = const Route: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts/$id/comments", "/posts/$id/comments", "/posts/$id/comments", "/posts/$id/comments", undefined, ResolveParams<"/posts/$id/comments">, AnyContext, AnyContext, AnyContext, ... 6 more ..., undefined>
Route.RouteExtensions<"/posts/$id/comments", "/posts/$id/comments">.useLoaderData: <RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, {
readonly PostsIdCommentsRoute: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, ... 7 more ..., undefined>, ... 15 more ..., undefined>;
}, unknown, unknown, unknown, undefined>, "never", false, RouterHistory, Record<...>>, unknown, boolean>(opts?: (UseLoaderDataBaseOptions<...> & OptionalStructuralSharing<...>) | undefined) => {
...;
}
useLoaderData();
return (
<const Suspense: React.ExoticComponent<React.SuspenseProps>Suspense React.SuspenseProps.fallback?: React.ReactNodefallback={<React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>Loading comments…</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>}>
<function Comments(props: {
comments: inferInfiniteQueryHandle<TRPC["post"]["comments"]>;
}): JSX.Element
Comments comments: TRPCInfiniteQueryHandle<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
comments={const loaderData: {
comments: TRPCInfiniteQueryHandle<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>;
}
loaderData.comments: TRPCInfiniteQueryHandle<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
comments} />
</const Suspense: React.ExoticComponent<React.SuspenseProps>Suspense>
);
}- Wrap the reader in
Suspense.Commentssuspends until the data arrives. Without your own boundary, the suspension reaches the nearest boundary above it: the route'spendingComponent, or a parent route's. That can blank much more of the page than you want. - The load never rejects for these queries. If a request fails,
Commentsthrows, and the route'serrorComponent(or the router's default) shows the error. - Pass
getNextPageParamto infinite queries. The loader runs again on client navigations, and if the cached pages are stale, it refetches every page the user loaded. That needsgetNextPageParamfrom page two on.
Waiting: ensure entries
The loader waits, so the route renders with the data in place:
import { function createFileRoute<TFilePath extends keyof FileRoutesByPath, TParentRoute extends AnyRoute = FileRoutesByPath[TFilePath]["parentRoute"], TId extends RouteConstraints["TId"] = FileRoutesByPath[TFilePath]["id"], TPath extends RouteConstraints["TPath"] = FileRoutesByPath[TFilePath]["path"], TFullPath extends RouteConstraints["TFullPath"] = FileRoutesByPath[TFilePath]["fullPath"]>(path?: TFilePath): FileRoute<TFilePath, TParentRoute, TId, TPath, TFullPath>["createRoute"]createFileRoute, function notFound(options?: NotFoundError): NotFoundErrornotFound } from '@tanstack/react-router';
import {
function ensureEntry<TDef extends ResolverDef>(procedure: DecorateQueryProcedure<TDef>, ...args: EntryArgs<TDef>): TRPCQueryEntry<TDef, "ensure">ensureEntry,
function isTRPCEntryError<TDef extends ResolverDef>(error: unknown, procedure: DecorateQueryProcedure<TDef>): error is TRPCEntryError<TDef> (+1 overload)isTRPCEntryError,
function loadHandles<TFeatureFlags extends FeatureFlags, TEntries extends Record<string, AnyTRPCEntry>>(trpc: DecorateRouterKeyable<TFeatureFlags>, entries: TEntries): Promise<TRPCLoadedHandles<TEntries>>loadHandles,
} from '@trpc/tanstack-react-query/unstable-handles';
import { function Post(props: {
post: inferQueryHandle<TRPC["post"]["byId"]>;
}): JSX.Element
Post } from '~/components/post';
export const const Route: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts/$id", "/posts/$id", "/posts/$id", "/posts/$id", undefined, ResolveParams<"/posts/$id">, AnyContext, AnyContext, AnyContext, {}, ({ context: { trpc }, params }: LoaderFnContext<Register, ... 8 more ..., undefined>) => Promise<...>, ... 4 more ..., undefined>
Route = createFileRoute<"/posts/$id", RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts/$id", "/posts/$id", "/posts/$id">(path?: "/posts/$id" | undefined): <TRegister, TSearchValidator, TParams, TRouteContextFn, TBeforeLoadFn, TLoaderDeps, TLoaderFn, TChildren, TSSR, TMiddlewares, THandlers>(options?: (ParamsOptions<...> & ... 1 more ... & UpdatableRouteOptions<...>) | undefined) => Route<...>
createFileRoute('/posts/$id')({
FilebaseRouteOptionsInterface<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 12 more ..., undefined>.loader?: Constrain<({ context: { trpc }, params }: LoaderFnContext<Register, ... 8 more ..., undefined>) => Promise<...>, RouteLoaderFn<...> | RouteLoaderObject<...>> | undefinedloader: async ({ LoaderFnContext<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 7 more ..., undefined>.context: {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}
context: { trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>
trpc }, params: {
id: string;
}
params }) => {
const [const handles: HandlesOf<{
post: TRPCQueryEntry<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "ensure">;
}>
handles] = await loadHandles<FeatureFlags, {
post: TRPCQueryEntry<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "ensure">;
}>(trpc: DecorateRouterKeyable<FeatureFlags>, entries: {
post: TRPCQueryEntry<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "ensure">;
}): Promise<...>
loadHandles(trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>
trpc, {
post: TRPCQueryEntry<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "ensure">
post: ensureEntry<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>(procedure: DecorateQueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>, input: {
id: string;
}, opts?: TRPCEntryOptions | undefined): TRPCQueryEntry<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "ensure">
ensureEntry(trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>
trpc.post: DecoratedRouterRecord<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>, {
...;
}> & DecorateRouterKeyable<...>
post.byId: DecorateQueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}> & Record<string, never>
byId, { id: stringid: params: {
id: string;
}
params.id: stringid }),
}).Promise<TRPCLoadedHandles<{ post: TRPCQueryEntry<{ input: { id: string; }; output: { id: string; title: string; authorId: string; }; transformer: true; errorShape: DefaultErrorShape; featureFlags: { keyPrefix: false; }; }, "ensure">; }>>.catch<never>(onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<TRPCLoadedHandles<{
post: TRPCQueryEntry<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "ensure">;
}>>
catch((cause: anycause) => {
if (isTRPCEntryError<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>(error: unknown, procedure: DecorateQueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>): error is TRPCEntryError<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}> (+1 overload)
isTRPCEntryError(cause: anycause, trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>
trpc.post: DecoratedRouterRecord<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>, {
...;
}> & DecorateRouterKeyable<...>
post.byId: DecorateQueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}> & Record<string, never>
byId) && cause: TRPCEntryError<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
cause.TRPCEntryError<{ input: { id: string; }; output: { id: string; title: string; authorId: string; }; transformer: true; errorShape: DefaultErrorShape; featureFlags: { keyPrefix: false; }; }>.code: "PARSE_ERROR" | "BAD_REQUEST" | "INTERNAL_SERVER_ERROR" | "NOT_IMPLEMENTED" | "BAD_GATEWAY" | "SERVICE_UNAVAILABLE" | "GATEWAY_TIMEOUT" | "UNAUTHORIZED" | "PAYMENT_REQUIRED" | "FORBIDDEN" | "NOT_FOUND" | "METHOD_NOT_SUPPORTED" | "TIMEOUT" | "CONFLICT" | "PRECONDITION_FAILED" | "PAYLOAD_TOO_LARGE" | "UNSUPPORTED_MEDIA_TYPE" | "UNPROCESSABLE_CONTENT" | "PRECONDITION_REQUIRED" | "TOO_MANY_REQUESTS" | "CLIENT_CLOSED_REQUEST" | undefinedcode === 'NOT_FOUND') {
throw function notFound(options?: NotFoundError): NotFoundErrornotFound();
}
throw cause: anycause;
});
return const handles: HandlesOf<{
post: TRPCQueryEntry<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "ensure">;
}>
handles;
},
UpdatableRouteOptionsExtensions.component?: RouteComponent | undefinedcomponent: function PostPage(): JSX.ElementPostPage,
});
function function PostPage(): JSX.ElementPostPage() {
const const loaderData: {
post: TRPCQueryHandle<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>;
}
loaderData = const Route: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts/$id", "/posts/$id", "/posts/$id", "/posts/$id", undefined, ResolveParams<"/posts/$id">, AnyContext, AnyContext, AnyContext, {}, ({ context: { trpc }, params }: LoaderFnContext<Register, ... 8 more ..., undefined>) => Promise<...>, ... 4 more ..., undefined>
Route.RouteExtensions<"/posts/$id", "/posts/$id">.useLoaderData: <RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, {
readonly PostsIdRoute: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, ... 7 more ..., undefined>, ... 15 more ..., undefined>;
}, unknown, unknown, unknown, undefined>, "never", false, RouterHistory, Record<...>>, unknown, boolean>(opts?: (UseLoaderDataBaseOptions<...> & OptionalStructuralSharing<...>) | undefined) => {
...;
}
useLoaderData();
return <function Post(props: {
post: inferQueryHandle<TRPC["post"]["byId"]>;
}): JSX.Element
Post post: TRPCQueryHandle<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
post={const loaderData: {
post: TRPCQueryHandle<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>;
}
loaderData.post: TRPCQueryHandle<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
post} />;
}- The data is in the cache before the route renders, so
Postshows the post at once. If the cache loses the post later,Postsuspends and shows the nearestSuspensefallback, or the route'spendingComponent: see When the cache loses the data. - The navigation waits for the loader. On a slow request, the router shows the route's
pendingComponent, if it has one. - The loader can act on errors, as here with
notFound(). - The handle carries no data. To use the data in the loader, for example for the route's
head, read it from the load'sdata:const [handles, data] = await loadHandles(trpc, …), and return what you need. See Using the data in a loader.
To wait for some queries and stream others in the same loader, list them all in one load: see Waiting for some, streaming others.
6. Read handles in components
Components read handles with fromHandle and useTRPC(). They're the same components you would write for the Next.js App Router, minus 'use client':
import { function useSuspenseQuery<TQueryFnData = unknown, TError = Error, TData = TQueryFnData, TQueryKey extends QueryKey = readonly unknown[]>(options: UseSuspenseQueryOptions<TQueryFnData, TError, TData, TQueryKey>, queryClient?: QueryClient): UseSuspenseQueryResult<TData, TError>useSuspenseQuery } from '@tanstack/react-query';
import { function fromHandle<TDef extends ResolverDef, TData = TDef["output"]>(trpc: DecorateRouterKeyable<NoInfer<TDef["featureFlags"]>>, handle: TRPCQueryHandle<TDef>, opts?: UnusedSkipTokenTRPCQueryOptionsIn<NoInfer<TDef["output"]>, TData, ErrorOf<TDef>, TDef["featureFlags"]>): UnusedSkipTokenTRPCQueryOptionsOut<TDef["output"], TData, ErrorOf<TDef>, TDef["featureFlags"]> (+1 overload)fromHandle } from '@trpc/tanstack-react-query/unstable-handles';
import type { type inferQueryHandle<TProcedure extends DecorateQueryProcedure<any>> = TProcedure extends DecorateQueryProcedure<infer TDef extends ResolverDef> ? TRPCQueryHandle<TDef> : neverinferQueryHandle } from '@trpc/tanstack-react-query/unstable-handles';
import { const useTRPC: () => TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
useTRPC, type type TRPC = DecoratedRouterRecord<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>, {
...;
}> & DecorateRouterKeyable<...>
TRPC } from '~/lib/trpc';
export function function Post(props: {
post: inferQueryHandle<TRPC["post"]["byId"]>;
}): JSX.Element
Post(props: {
post: inferQueryHandle<TRPC["post"]["byId"]>;
}
props: { post: TRPCQueryHandle<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
post: type inferQueryHandle<TProcedure extends DecorateQueryProcedure<any>> = TProcedure extends DecorateQueryProcedure<infer TDef extends ResolverDef> ? TRPCQueryHandle<TDef> : neverinferQueryHandle<type TRPC = DecoratedRouterRecord<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>, {
...;
}> & DecorateRouterKeyable<...>
TRPC['post']['byId']> }) {
const const trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
trpc = function useTRPC(): TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
useTRPC();
const const postQuery: UseSuspenseQueryResult<{
id: string;
title: string;
authorId: string;
}, ErrorOf<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>>
postQuery = useSuspenseQuery<{
id: string;
title: string;
authorId: string;
}, ErrorOf<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>, {
id: string;
title: string;
authorId: string;
}, TRPCQueryKeyWithoutPrefix>(options: UseSuspenseQueryOptions<{
id: string;
title: string;
authorId: string;
}, ErrorOf<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>, {
id: string;
title: string;
authorId: string;
}, TRPCQueryKeyWithoutPrefix>, queryClient?: QueryClient): UseSuspenseQueryResult<...>
useSuspenseQuery(fromHandle<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, {
id: string;
title: string;
authorId: string;
}>(trpc: DecorateRouterKeyable<NoInfer<{
keyPrefix: false;
}>>, handle: TRPCQueryHandle<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>, opts?: UnusedSkipTokenTRPCQueryOptionsIn<NoInfer<{
id: string;
title: string;
authorId: string;
}>, {
id: string;
title: string;
authorId: string;
}, ErrorOf<...>, {
keyPrefix: false;
}> | undefined): UnusedSkipTokenTRPCQueryOptionsOut<...> (+1 overload)
fromHandle(const trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
trpc, props: {
post: inferQueryHandle<TRPC["post"]["byId"]>;
}
props.post: TRPCQueryHandle<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
post));
return <React.JSX.IntrinsicElements.h1: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h1>{const postQuery: UseSuspenseQueryResult<{
id: string;
title: string;
authorId: string;
}, ErrorOf<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>>
postQuery.data: {
id: string;
title: string;
authorId: string;
}
data.title: stringtitle}</React.JSX.IntrinsicElements.h1: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h1>;
}Route loader
loader: async ({ params: { id } }) => { const [handles] = await loadHandles(trpc, { post: prefetchEntry(trpc.post.byId, { id }), comments: prefetchInfiniteEntry( trpc.post.comments, …, ), }); return handles; }
Handles
None yet
Components
Not rendered yet
Not rendered yet
Requests
Start every query from the URL
A loader only sees the URL, so give it everything a page's queries need: path params for what the page is about, and search params for filters, sort and page. With validateSearch and loaderDeps, the loader starts the right query on every navigation, before the route renders:
import { function useSuspenseQuery<TQueryFnData = unknown, TError = Error, TData = TQueryFnData, TQueryKey extends QueryKey = readonly unknown[]>(options: UseSuspenseQueryOptions<TQueryFnData, TError, TData, TQueryKey>, queryClient?: QueryClient): UseSuspenseQueryResult<TData, TError>useSuspenseQuery } from '@tanstack/react-query';
import { function createFileRoute<TFilePath extends keyof FileRoutesByPath, TParentRoute extends AnyRoute = FileRoutesByPath[TFilePath]["parentRoute"], TId extends RouteConstraints["TId"] = FileRoutesByPath[TFilePath]["id"], TPath extends RouteConstraints["TPath"] = FileRoutesByPath[TFilePath]["path"], TFullPath extends RouteConstraints["TFullPath"] = FileRoutesByPath[TFilePath]["fullPath"]>(path?: TFilePath): FileRoute<TFilePath, TParentRoute, TId, TPath, TFullPath>["createRoute"]createFileRoute } from '@tanstack/react-router';
import { function loadHandles<TFeatureFlags extends FeatureFlags, TEntries extends Record<string, AnyTRPCEntry>>(trpc: DecorateRouterKeyable<TFeatureFlags>, entries: TEntries): Promise<TRPCLoadedHandles<TEntries>>loadHandles, function prefetchEntry<TDef extends ResolverDef>(procedure: DecorateQueryProcedure<TDef>, ...args: EntryArgs<TDef>): TRPCQueryEntry<TDef, "prefetch">prefetchEntry, function fromHandle<TDef extends ResolverDef, TData = TDef["output"]>(trpc: DecorateRouterKeyable<NoInfer<TDef["featureFlags"]>>, handle: TRPCQueryHandle<TDef>, opts?: UnusedSkipTokenTRPCQueryOptionsIn<NoInfer<TDef["output"]>, TData, ErrorOf<TDef>, TDef["featureFlags"]>): UnusedSkipTokenTRPCQueryOptionsOut<TDef["output"], TData, ErrorOf<TDef>, TDef["featureFlags"]> (+1 overload)fromHandle } from '@trpc/tanstack-react-query/unstable-handles';
import { import zz } from 'zod';
import { const useTRPC: () => TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
useTRPC } from '../lib/trpc';
export const const Route: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts", "/posts", "/posts", "/posts", z.ZodObject<{
sort: z.ZodDefault<z.ZodEnum<{
new: "new";
top: "top";
}>>;
page: z.ZodDefault<z.ZodNumber>;
}, z.core.$strip>, ResolveParams<"/posts">, ... 9 more ..., undefined>
Route = createFileRoute<"/posts", RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts", "/posts", "/posts">(path?: "/posts" | undefined): <TRegister, TSearchValidator, TParams, TRouteContextFn, TBeforeLoadFn, TLoaderDeps, TLoaderFn, TChildren, TSSR, TMiddlewares, THandlers>(options?: (ParamsOptions<...> & ... 1 more ... & UpdatableRouteOptions<...>) | undefined) => Route<...>
createFileRoute('/posts')({
FilebaseRouteOptionsInterface<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 12 more ..., undefined>.validateSearch?: Constrain<z.ZodObject<{
sort: z.ZodDefault<z.ZodEnum<{
new: "new";
top: "top";
}>>;
page: z.ZodDefault<z.ZodNumber>;
}, z.core.$strip>, AnyValidator, DefaultValidator>
validateSearch: import zz.function object<{
sort: z.ZodDefault<z.ZodEnum<{
new: "new";
top: "top";
}>>;
page: z.ZodDefault<z.ZodNumber>;
}>(shape?: {
sort: z.ZodDefault<z.ZodEnum<{
new: "new";
top: "top";
}>>;
page: z.ZodDefault<z.ZodNumber>;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueUnrecognizedKeys | z.core.$ZodIssueInvalidType<unknown>>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
sort: z.ZodDefault<z.ZodEnum<{
new: "new";
top: "top";
}>>;
page: z.ZodDefault<z.ZodNumber>;
}, z.core.$strip>
object({
sort: z.ZodDefault<z.ZodEnum<{
new: "new";
top: "top";
}>>
sort: import zz.enum<readonly ["new", "top"]>(values: readonly ["new", "top"], params?: string | z.core.$ZodEnumParams): z.ZodEnum<{
new: "new";
top: "top";
}> (+1 overload)
export enum
enum(['new', 'top']).ZodType<any, any, $ZodEnumInternals<{ new: "new"; top: "top"; }>>.default(def: "new" | "top"): z.ZodDefault<z.ZodEnum<{
new: "new";
top: "top";
}>> (+1 overload)
default('new'),
page: z.ZodDefault<z.ZodNumber>page: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber()._ZodNumber<$ZodNumberInternals<number>>.int(params?: string | z.core.$ZodCheckNumberFormatParams): z.ZodNumberint()._ZodNumber<$ZodNumberInternals<number>>.positive(params?: string | z.core.$ZodCheckGreaterThanParams): z.ZodNumberpositive().ZodType<any, any, $ZodNumberInternals<number>>.default(def: number): z.ZodDefault<z.ZodNumber> (+1 overload)default(1),
}),
FilebaseRouteOptionsInterface<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 12 more ..., undefined>.loaderDeps?: ((opts: FullSearchSchemaOption<RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, z.ZodObject<{
sort: z.ZodDefault<z.ZodEnum<{
new: "new";
top: "top";
}>>;
page: z.ZodDefault<z.ZodNumber>;
}, z.core.$strip>>) => {
sort: "new" | "top";
page: number;
}) | undefined
loaderDeps: ({ search: {
sort: "new" | "top";
page: number;
}
search }) => search: {
sort: "new" | "top";
page: number;
}
search,
FilebaseRouteOptionsInterface<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 12 more ..., undefined>.loader?: Constrain<({ context: { trpc }, deps }: LoaderFnContext<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts", ResolveParams<"/posts">, {
sort: "new" | "top";
page: number;
}, AnyContext, AnyContext, AnyContext, unknown, undefined>) => Promise<...>, RouteLoaderFn<...> | RouteLoaderObject<...>> | undefined
loader: async ({ LoaderFnContext<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 7 more ..., undefined>.context: {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}
context: { trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>
trpc }, deps: {
sort: "new" | "top";
page: number;
}
deps }) => {
const [const handles: HandlesOf<{
posts: TRPCQueryEntry<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "prefetch">;
}>
handles] = await loadHandles<FeatureFlags, {
posts: TRPCQueryEntry<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "prefetch">;
}>(trpc: DecorateRouterKeyable<FeatureFlags>, entries: {
posts: TRPCQueryEntry<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "prefetch">;
}): Promise<...>
loadHandles(trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>
trpc, {
posts: TRPCQueryEntry<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "prefetch">
posts: prefetchEntry<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>(procedure: DecorateQueryProcedure<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>, input: {
sort: "new" | "top";
page: number;
}, opts?: TRPCEntryOptions | undefined): TRPCQueryEntry<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "prefetch">
prefetchEntry(trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>
trpc.post: DecoratedRouterRecord<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>, {
...;
}> & DecorateRouterKeyable<...>
post.list: DecorateQueryProcedure<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}> & Record<string, never>
list, deps: {
sort: "new" | "top";
page: number;
}
deps),
});
return const handles: HandlesOf<{
posts: TRPCQueryEntry<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "prefetch">;
}>
handles;
},
UpdatableRouteOptionsExtensions.component?: RouteComponent | undefinedcomponent: function PostsPage(): JSX.ElementPostsPage,
});
function function PostsPage(): JSX.ElementPostsPage() {
const const trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
trpc = function useTRPC(): TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
useTRPC();
const const loaderData: {
posts: TRPCQueryHandle<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>;
}
loaderData = const Route: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts", "/posts", "/posts", "/posts", z.ZodObject<{
sort: z.ZodDefault<z.ZodEnum<{
new: "new";
top: "top";
}>>;
page: z.ZodDefault<z.ZodNumber>;
}, z.core.$strip>, ResolveParams<"/posts">, ... 9 more ..., undefined>
Route.RouteExtensions<"/posts", "/posts">.useLoaderData: <RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, {
readonly PostsRoute: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, ... 7 more ..., undefined>, ... 15 more ..., undefined>;
}, unknown, unknown, unknown, undefined>, "never", false, RouterHistory, Record<...>>, unknown, boolean>(opts?: (UseLoaderDataBaseOptions<...> & OptionalStructuralSharing<...>) | undefined) => {
...;
}
useLoaderData();
const const postsQuery: UseSuspenseQueryResult<{
id: string;
title: string;
}[], ErrorOf<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>>
postsQuery = useSuspenseQuery<{
id: string;
title: string;
}[], ErrorOf<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>, {
id: string;
title: string;
}[], TRPCQueryKeyWithoutPrefix>(options: UseSuspenseQueryOptions<{
id: string;
title: string;
}[], ErrorOf<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>, {
id: string;
title: string;
}[], TRPCQueryKeyWithoutPrefix>, queryClient?: QueryClient): UseSuspenseQueryResult<...>
useSuspenseQuery(
fromHandle<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, {
id: string;
title: string;
}[]>(trpc: DecorateRouterKeyable<NoInfer<{
keyPrefix: false;
}>>, handle: TRPCQueryHandle<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>, opts?: UnusedSkipTokenTRPCQueryOptionsIn<NoInfer<{
id: string;
title: string;
}[]>, {
id: string;
title: string;
}[], ErrorOf<...>, {
keyPrefix: false;
}> | undefined): UnusedSkipTokenTRPCQueryOptionsOut<...> (+1 overload)
fromHandle(const trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
trpc, const loaderData: {
posts: TRPCQueryHandle<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>;
}
loaderData.posts: TRPCQueryHandle<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
posts),
);
return (
<React.JSX.IntrinsicElements.ul: React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>ul>
{const postsQuery: UseSuspenseQueryResult<{
id: string;
title: string;
}[], ErrorOf<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>>
postsQuery.data: {
id: string;
title: string;
}[]
data.Array<{ id: string; title: string; }>.map<JSX.Element>(callbackfn: (value: {
id: string;
title: string;
}, index: number, array: {
id: string;
title: string;
}[]) => JSX.Element, thisArg?: any): JSX.Element[]
map((post: {
id: string;
title: string;
}
post) => (
<React.JSX.IntrinsicElements.li: React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>li React.Attributes.key?: React.Key | null | undefinedkey={post: {
id: string;
title: string;
}
post.id: stringid}>{post: {
id: string;
title: string;
}
post.title: stringtitle}</React.JSX.IntrinsicElements.li: React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>li>
))}
</React.JSX.IntrinsicElements.ul: React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>ul>
);
}A link to /posts?sort=top starts the query for the top posts as the navigation begins. The back button brings back the last sort, and a shared link opens the same view. Keep the sort in useState and you lose all three, since the loader can't see component state.
- Typesafe APIs without codegen▲ 3
- Streaming responses over HTTP▲ 40
- Subscriptions with server-sent events▲ 27
What the loader sees
loaderDeps: ({ search }) => search,
// { sort: 'new', page: 1 }
loader: ({ deps }) =>
loadHandles(trpc, {
posts: prefetchEntry(trpc.post.list, deps),
}),Requests
- loaderpost.list({ sort: 'new', page: 1 })done
If a query can't take its input from the URL, for example because it needs an id from another query's result, reshape the procedure: see If the URL isn't enough, reshape the procedure.
Handles in beforeLoad
beforeLoad can return handles too, for example to start a query in a parent route and read it in child routes. TanStack Router checks that values returned from beforeLoad are serializable, and handles pass that check as they are: their query keys and inputs are typed from the procedure.
Escape hatch: loading a dependent query before render (TanStack Router)
beforeLoad runs before the loader, so it can wait for the post and hand its data to the loader through the route context. The loader then starts more by the post's author from post.authorId:
import { function createFileRoute<TFilePath extends keyof FileRoutesByPath, TParentRoute extends AnyRoute = FileRoutesByPath[TFilePath]["parentRoute"], TId extends RouteConstraints["TId"] = FileRoutesByPath[TFilePath]["id"], TPath extends RouteConstraints["TPath"] = FileRoutesByPath[TFilePath]["path"], TFullPath extends RouteConstraints["TFullPath"] = FileRoutesByPath[TFilePath]["fullPath"]>(path?: TFilePath): FileRoute<TFilePath, TParentRoute, TId, TPath, TFullPath>["createRoute"]createFileRoute } from '@tanstack/react-router';
import {
function ensureInfiniteEntry<TDef extends ResolverDef>(procedure: DecorateInfiniteQueryProcedure<TDef>, input: TDef["input"], opts?: TRPCInfiniteEntryOptions<TDef>): TRPCInfiniteQueryEntry<TDef, "ensure">ensureInfiniteEntry,
function fetchEntry<TDef extends ResolverDef>(procedure: DecorateQueryProcedure<TDef>, ...args: EntryArgs<TDef>): TRPCQueryEntry<TDef, "fetch">fetchEntry,
function loadHandles<TFeatureFlags extends FeatureFlags, TEntries extends Record<string, AnyTRPCEntry>>(trpc: DecorateRouterKeyable<TFeatureFlags>, entries: TEntries): Promise<TRPCLoadedHandles<TEntries>>loadHandles,
function prefetchInfiniteEntry<TDef extends ResolverDef>(procedure: DecorateInfiniteQueryProcedure<TDef>, input: TDef["input"], opts?: TRPCInfiniteEntryOptions<TDef>): TRPCInfiniteQueryEntry<TDef, "prefetch">prefetchInfiniteEntry,
} from '@trpc/tanstack-react-query/unstable-handles';
import { function Comments(props: {
comments: inferInfiniteQueryHandle<TRPC["post"]["comments"]>;
}): JSX.Element
Comments } from '~/components/comments';
import { function MoreByAuthor(props: {
posts: inferInfiniteQueryHandle<TRPC["post"]["byAuthor"]>;
}): JSX.Element
MoreByAuthor } from '~/components/more-by-author';
import { function Post(props: {
post: inferQueryHandle<TRPC["post"]["byId"]>;
}): JSX.Element
Post } from '~/components/post';
export const const Route: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts/$id", "/posts/$id", "/posts/$id", "/posts/$id", undefined, ResolveParams<"/posts/$id">, AnyContext, AnyContext, ({ context: { trpc }, params }: BeforeLoadContextOptions<Register, RootRoute<Register, undefined, ... 9 more ..., undefined>, ... 6 more ..., undefined>) => Promise<...>, ... 6 more ..., undefined>
Route = createFileRoute<"/posts/$id", RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts/$id", "/posts/$id", "/posts/$id">(path?: "/posts/$id" | undefined): <TRegister, TSearchValidator, TParams, TRouteContextFn, TBeforeLoadFn, TLoaderDeps, TLoaderFn, TChildren, TSSR, TMiddlewares, THandlers>(options?: (ParamsOptions<...> & ... 1 more ... & UpdatableRouteOptions<...>) | undefined) => Route<...>
createFileRoute('/posts/$id')({
FilebaseRouteOptionsInterface<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 12 more ..., undefined>.beforeLoad?: Constrain<({ context: { trpc }, params }: BeforeLoadContextOptions<Register, RootRoute<Register, undefined, ... 9 more ..., undefined>, ... 6 more ..., undefined>) => Promise<...>, (ctx: BeforeLoadContextOptions<...>) => Promise<...>> | undefinedbeforeLoad: async ({ BeforeLoadContextOptions<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 6 more ..., undefined>.context: {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}
context: { trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>
trpc }, params: {
id: string;
}
params }) => {
const [const handles: HandlesOf<{
post: TRPCQueryEntry<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "fetch">;
comments: TRPCInfiniteQueryEntry<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "prefetch">;
}>
handles, const data: DataOf<{
post: TRPCQueryEntry<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "fetch">;
comments: TRPCInfiniteQueryEntry<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "prefetch">;
}>
data] = await loadHandles<FeatureFlags, {
post: TRPCQueryEntry<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "fetch">;
comments: TRPCInfiniteQueryEntry<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "prefetch">;
}>(trpc: DecorateRouterKeyable<...>, entries: {
post: TRPCQueryEntry<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "fetch">;
comments: TRPCInfiniteQueryEntry<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "prefetch">;
}): Promise<...>
loadHandles(trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>
trpc, {
// Fresh, since the loader builds another query's input from it
post: TRPCQueryEntry<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "fetch">
post: fetchEntry<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>(procedure: DecorateQueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>, input: {
id: string;
}, opts?: TRPCEntryOptions | undefined): TRPCQueryEntry<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "fetch">
fetchEntry(trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>
trpc.post: DecoratedRouterRecord<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>, {
...;
}> & DecorateRouterKeyable<...>
post.byId: DecorateQueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}> & Record<string, never>
byId, { id: stringid: params: {
id: string;
}
params.id: stringid }),
comments: TRPCInfiniteQueryEntry<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "prefetch">
comments: prefetchInfiniteEntry<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>(procedure: DecorateInfiniteQueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>, input: {
...;
}, opts?: TRPCInfiniteEntryOptions<...> | undefined): TRPCInfiniteQueryEntry<...>
prefetchInfiniteEntry(
trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>
trpc.post: DecoratedRouterRecord<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>, {
...;
}> & DecorateRouterKeyable<...>
post.comments: DecorateQueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}> & DecorateInfiniteQueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
comments,
{ postId: stringpostId: params: {
id: string;
}
params.id: stringid },
{ getNextPageParam: (last: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
}) => number | null
getNextPageParam: (last: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
}
last) => last: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
}
last.nextCursor: number | nullnextCursor },
),
});
return { postHandles: HandlesOf<{
post: TRPCQueryEntry<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "fetch">;
comments: TRPCInfiniteQueryEntry<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "prefetch">;
}>
postHandles: const handles: HandlesOf<{
post: TRPCQueryEntry<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "fetch">;
comments: TRPCInfiniteQueryEntry<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "prefetch">;
}>
handles, post: {
id: string;
title: string;
authorId: string;
}
post: const data: DataOf<{
post: TRPCQueryEntry<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "fetch">;
comments: TRPCInfiniteQueryEntry<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "prefetch">;
}>
data.post: {
id: string;
title: string;
authorId: string;
}
post };
},
FilebaseRouteOptionsInterface<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 12 more ..., undefined>.loader?: Constrain<({ context: { trpc, postHandles, post } }: LoaderFnContext<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts/$id", ResolveParams<"/posts/$id">, {}, AnyContext, AnyContext, ({ context: { trpc }, params }: BeforeLoadContextOptions<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, ... 8 more ..., undefined>, ... 6 more ..., undefined>) => Promise<...>, unknown, undefined>) => Promise<...>, RouteLoaderFn<...> | RouteLoaderObject<...>> | undefined
loader: async ({ LoaderFnContext<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 7 more ..., undefined>.context: {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
postHandles: HandlesOf<{
post: TRPCQueryEntry<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "fetch">;
comments: TRPCInfiniteQueryEntry<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "prefetch">;
}>;
post: {
id: string;
title: string;
authorId: string;
};
}
context: { trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>
trpc, postHandles: HandlesOf<{
post: TRPCQueryEntry<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "fetch">;
comments: TRPCInfiniteQueryEntry<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "prefetch">;
}>
postHandles, post: {
id: string;
title: string;
authorId: string;
}
post } }) => {
// Starts only after beforeLoad has the post
const [const handles: HandlesOf<{
moreByAuthor: TRPCInfiniteQueryEntry<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "ensure">;
}>
handles] = await loadHandles<FeatureFlags, {
moreByAuthor: TRPCInfiniteQueryEntry<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "ensure">;
}>(trpc: DecorateRouterKeyable<FeatureFlags>, entries: {
moreByAuthor: TRPCInfiniteQueryEntry<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "ensure">;
}): Promise<...>
loadHandles(trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>
trpc, {
moreByAuthor: TRPCInfiniteQueryEntry<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "ensure">
moreByAuthor: ensureInfiniteEntry<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>(procedure: DecorateInfiniteQueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>, input: {
...;
}, opts?: TRPCInfiniteEntryOptions<...> | undefined): TRPCInfiniteQueryEntry<...>
ensureInfiniteEntry(
trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>>;
}>>>
trpc.post: DecoratedRouterRecord<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
byAuthor: QueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<...>;
list: QueryProcedure<...>;
}>, {
...;
}> & DecorateRouterKeyable<...>
post.byAuthor: DecorateQueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}> & DecorateInfiniteQueryProcedure<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
byAuthor,
{ authorId: stringauthorId: post: {
id: string;
title: string;
authorId: string;
}
post.authorId: stringauthorId },
{ getNextPageParam: (last: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
}) => number | null
getNextPageParam: (last: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
}
last) => last: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
}
last.nextCursor: number | nullnextCursor },
),
});
return { ...postHandles: HandlesOf<{
post: TRPCQueryEntry<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "fetch">;
comments: TRPCInfiniteQueryEntry<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "prefetch">;
}>
postHandles, ...const handles: HandlesOf<{
moreByAuthor: TRPCInfiniteQueryEntry<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, "ensure">;
}>
handles };
},
UpdatableRouteOptionsExtensions.component?: RouteComponent | undefinedcomponent: function PostPage(): JSX.ElementPostPage,
});
function function PostPage(): JSX.ElementPostPage() {
const const loaderData: {
moreByAuthor: TRPCInfiniteQueryHandle<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>;
post: TRPCQueryHandle<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>;
comments: TRPCInfiniteQueryHandle<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | ... 1 more ... | undefined;
};
output: {
...;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>;
}
loaderData = const Route: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts/$id", "/posts/$id", "/posts/$id", "/posts/$id", undefined, ResolveParams<"/posts/$id">, AnyContext, AnyContext, ({ context: { trpc }, params }: BeforeLoadContextOptions<Register, RootRoute<Register, undefined, ... 9 more ..., undefined>, ... 6 more ..., undefined>) => Promise<...>, ... 6 more ..., undefined>
Route.RouteExtensions<"/posts/$id", "/posts/$id">.useLoaderData: <RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, {
readonly PostsIdRoute: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, ... 7 more ..., undefined>, ... 15 more ..., undefined>;
}, unknown, unknown, unknown, undefined>, "never", false, RouterHistory, Record<...>>, unknown, boolean>(opts?: (UseLoaderDataBaseOptions<...> & OptionalStructuralSharing<...>) | undefined) => {
...;
}
useLoaderData();
return (
<>
<function Post(props: {
post: inferQueryHandle<TRPC["post"]["byId"]>;
}): JSX.Element
Post post: TRPCQueryHandle<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
post={const loaderData: {
moreByAuthor: TRPCInfiniteQueryHandle<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>;
post: TRPCQueryHandle<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>;
comments: TRPCInfiniteQueryHandle<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | ... 1 more ... | undefined;
};
output: {
...;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>;
}
loaderData.post: TRPCQueryHandle<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
post} />
<function Comments(props: {
comments: inferInfiniteQueryHandle<TRPC["post"]["comments"]>;
}): JSX.Element
Comments comments: TRPCInfiniteQueryHandle<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | ... 1 more ... | undefined;
};
output: {
...;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
comments={const loaderData: {
moreByAuthor: TRPCInfiniteQueryHandle<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>;
post: TRPCQueryHandle<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>;
comments: TRPCInfiniteQueryHandle<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | ... 1 more ... | undefined;
};
output: {
...;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>;
}
loaderData.comments: TRPCInfiniteQueryHandle<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | ... 1 more ... | undefined;
};
output: {
...;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
comments} />
<function MoreByAuthor(props: {
posts: inferInfiniteQueryHandle<TRPC["post"]["byAuthor"]>;
}): JSX.Element
MoreByAuthor posts: TRPCInfiniteQueryHandle<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
posts={const loaderData: {
moreByAuthor: TRPCInfiniteQueryHandle<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>;
post: TRPCQueryHandle<{
input: {
id: string;
};
output: {
id: string;
title: string;
authorId: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>;
comments: TRPCInfiniteQueryHandle<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | ... 1 more ... | undefined;
};
output: {
...;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>;
}
loaderData.moreByAuthor: TRPCInfiniteQueryHandle<{
input: {
authorId: string;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
title: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
moreByAuthor} />
</>
);
}This serialises the loads: more by the author can't start until the post has arrived. Prefer one loadHandles that starts both from the URL, or read the dependent query in the component, which fetches it in render instead. Reach for beforeLoad only when the dependent data must be in the cache before the page renders.
Don't return handles from server functions
A server function runs with its own QueryClient, which never reaches the page. Return data from server functions, and load handles in loaders. In development, tRPC warns when it renders a handle whose data never reached the page's QueryClient.