Trpc
React Query & Tanstack Query
How to work with React Query & Tanstack Query
npm install @trpc/server @trpc/client @trpc/tanstack-react-query @tanstack/react-query@latest zod client-only server-onlyimport { createTRPCContext } from "@/trpc/init";
import { appRouter } from "@/trpc/routers/_app";
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
const handler = (req: Request) =>
fetchRequestHandler({
endpoint: "/api/trpc",
req,
router: appRouter,
createContext: createTRPCContext,
});
export { handler as GET, handler as POST };import { cache } from "react";
import { headers } from "next/headers";
import { initTRPC, TRPCError } from "@trpc/server";
const createTRPCContextInner = async () => {
return {};
};
export const createTRPCContext = cache(createTRPCContextInner);
export const createTRPCContextForRoute = createTRPCContextInner;
// Avoid exporting the entire t-object
// since it's not very descriptive.
// For instance, the use of a t variable
// is common in i18n libraries.
const t = initTRPC.create({
/**
* @see https://trpc.io/docs/server/data-transformers
*/
// transformer: superjson,
});
// Base router and procedure helpers
export const createTRPCRouter = t.router;
export const createCallerFactory = t.createCallerFactory;
// Infer the context type from createTRPCContextInner
type Context = Awaited<ReturnType<typeof createTRPCContextInner>>;
export const baseProcedure = t.procedure;import "server-only"; // <-- ensure this file cannot be imported from the client
import { cache } from "react";
import { createTRPCOptionsProxy } from "@trpc/tanstack-react-query";
import { makeQueryClient } from "./client";
import { createTRPCContext } from "./init";
import { appRouter } from "./routers/_app";
// IMPORTANT: Create a stable getter for the query client that
// will return the same client during the same request.
export const getQueryClient = cache(makeQueryClient);
export const trpc = createTRPCOptionsProxy({
ctx: createTRPCContext,
router: appRouter,
queryClient: getQueryClient,
});
export const caller = appRouter.createCaller(createTRPCContext);"use client";
// ^-- to make sure we can mount the Provider from a server component
import { useEffect, useState } from "react";
import {
defaultShouldDehydrateQuery,
MutationCache,
QueryClient,
QueryClientProvider,
} from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { createTRPCClient, httpBatchLink } from "@trpc/client";
import { createTRPCContext } from "@trpc/tanstack-react-query";
// import superjson from 'superjson';
import type { AppRouter } from "./routers/_app";
export function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
staleTime: 30 * 1000,
retry: false,
},
dehydrate: {
// serializeData: superjson.serialize,
shouldDehydrateQuery: (query) =>
defaultShouldDehydrateQuery(query) ||
query.state.status === "pending",
},
hydrate: {
// deserializeData: superjson.deserialize,
},
},
mutationCache: new MutationCache({
onSuccess: (data: any) => {
// If the data has an error, throw an error
if (data.error) {
throw new Error(data.error);
}
},
}),
});
}
export const { TRPCProvider, useTRPC } = createTRPCContext<AppRouter>();
let browserQueryClient: QueryClient;
export let queryClient: QueryClient;
function getQueryClient() {
if (typeof window === "undefined") {
// Server: always make a new query client
return makeQueryClient();
}
// Browser: make a new query client if we don't already have one
// This is very important, so we don't re-make a new client if React
// suspends during the initial render. This may not be needed if we
// have a suspense boundary BELOW the creation of the query client
if (!browserQueryClient) {
const client = makeQueryClient();
browserQueryClient = client;
queryClient = client;
}
return browserQueryClient;
}
function getUrl() {
const base = (() => {
if (typeof window !== "undefined") return "";
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
return "http://localhost:3000";
})();
return `${base}/api/trpc`;
}
// Export raw tRPC client for direct client-side calls (not through React Query)
export let trpcClient:
| ReturnType<typeof createTRPCClient<AppRouter>>
| undefined;
export function TRPCReactProvider(
props: Readonly<{
children: React.ReactNode;
}>
) {
// NOTE: Avoid useState when initializing the query client if you don't
// have a suspense boundary between this and the code that may
// suspend because React will throw away the client on the initial
// render if it suspends and there is no boundary
const queryClient = getQueryClient();
const [client] = useState(() => {
const client = createTRPCClient<AppRouter>({
links: [
httpBatchLink({
// transformer: superjson, <-- if you use a data transformer
url: getUrl(),
}),
],
});
// Store the raw client for direct calls
if (typeof window !== "undefined") {
trpcClient = client;
}
return client;
});
return (
<QueryClientProvider client={queryClient}>
<TRPCProvider trpcClient={client} queryClient={queryClient}>
<StoredTRPC>
{props.children}
<ReactQueryDevtools />
</StoredTRPC>
</TRPCProvider>
</QueryClientProvider>
);
}
// Store the TRPC instance so it can be accessed without calling the hook
// This is useful for queryOptions() and mutationOptions() which aren't hooks
export let storedTRPC: ReturnType<typeof useTRPC> | undefined;
/**
* Internal component that stores the TRPC instance
* Automatically initializes storedTRPC when TRPCReactProvider mounts
*/
function StoredTRPC({ children }: { children: React.ReactNode }) {
const trpc = useTRPC();
useEffect(() => {
if (typeof window !== "undefined") {
storedTRPC = trpc;
}
}, [trpc]);
return <>{children}</>;
}import { inferRouterInputs, inferRouterOutputs } from "@trpc/server";
import { baseProcedure, createTRPCRouter } from "../init";
export const appRouter = createTRPCRouter({
hello: baseProcedure.query(() => {
return {
message: "Hello, world!",
};
}),
});
// export type definition of API
export type AppRouter = typeof appRouter;
export type RouterOutputs = inferRouterOutputs<AppRouter>;
export type RouterInputs = inferRouterInputs<AppRouter>;