Files
calendar/apps/web/app/_trpc/trpc-provider.tsx
T
Benny JooandGitHub 6ea879f265 perf: run ssrInit() at root layout (#19460)
* wip

* remove ssrInit unneeded

* refactor

* revert unneeded changes

* update booking-page-wrapper layout
2025-02-25 06:08:23 -05:00

137 lines
4.6 KiB
TypeScript

"use client";
import { type DehydratedState, QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { HydrateClient } from "app/_trpc/HydrateClient";
import { trpc } from "app/_trpc/client";
import { useState } from "react";
import superjson from "superjson";
import { httpBatchLink, httpLink, loggerLink, splitLink, TRPCClientError } from "@calcom/trpc/client";
import { ENDPOINTS } from "@calcom/trpc/react/shared";
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
export type Endpoint = (typeof ENDPOINTS)[number];
const MAX_QUERY_RETRIES = 3;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const resolveEndpoint = (links: any) => {
// TODO: Update our trpc routes so they are more clear.
// This function parses paths like the following and maps them
// to the correct API endpoints.
// - viewer.me - 2 segment paths like this are for logged in requests
// - viewer.public.i18n - 3 segments paths can be public or authed
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (ctx: any) => {
const parts = ctx.op.path.split(".");
let endpoint;
let path = "";
if (parts.length == 2) {
endpoint = parts[0] as keyof typeof links;
path = parts[1];
} else {
endpoint = parts[1] as keyof typeof links;
path = parts.splice(2, parts.length - 2).join(".");
}
return links[endpoint]({ ...ctx, op: { ...ctx.op, path } });
};
};
const isTRPCClientError = (cause: unknown): cause is TRPCClientError<AppRouter> => {
return cause instanceof TRPCClientError;
};
type Props = {
children: React.ReactNode;
dehydratedState: DehydratedState;
};
export const TrpcProvider = ({ children, dehydratedState }: Props) => {
const [queryClient] = useState(
() =>
new QueryClient({
// these configurations are copied from "packages/trpc/react/trpc.ts"
defaultOptions: {
queries: {
/**
* 1s should be enough to just keep identical query waterfalls low
* @example if one page components uses a query that is also used further down the tree
*/
staleTime: 1000,
/**
* Retry `useQuery()` calls depending on this function
*/
retry(failureCount, error) {
if (isTRPCClientError(error) && error.data) {
const { code } = error.data;
if (code === "BAD_REQUEST" || code === "FORBIDDEN" || code === "UNAUTHORIZED") {
// if input data is wrong or you're not authorized there's no point retrying a query
return false;
}
}
return failureCount < MAX_QUERY_RETRIES;
},
},
},
})
);
const url =
typeof window !== "undefined"
? "/api/trpc"
: process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}/api/trpc`
: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/api/trpc`;
const [trpcClient] = useState(() =>
trpc.createClient({
links: [
// adds pretty logs to your console in development and logs errors in production
loggerLink({
enabled: (opts) =>
(typeof process.env.NEXT_PUBLIC_LOGGER_LEVEL === "number" &&
process.env.NEXT_PUBLIC_LOGGER_LEVEL >= 0) ||
(opts.direction === "down" && opts.result instanceof Error),
}),
splitLink({
// check for context property `skipBatch`
condition: (op) => !!op.context.skipBatch,
// when condition is true, use normal request
true: (runtime) => {
const links = Object.fromEntries(
ENDPOINTS.map((endpoint) => [
endpoint,
httpLink({
url: `${url}/${endpoint}`,
})(runtime),
])
);
return resolveEndpoint(links);
},
// when condition is false, use batch request
false: (runtime) => {
const links = Object.fromEntries(
ENDPOINTS.map((endpoint) => [
endpoint,
httpBatchLink({
url: `${url}/${endpoint}`,
})(runtime),
])
);
return resolveEndpoint(links);
},
}),
],
transformer: superjson,
})
);
return (
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
<HydrateClient state={dehydratedState}>{children}</HydrateClient>
</QueryClientProvider>
</trpc.Provider>
);
};