fix: trpc/react-query — only one query client should exist (#18179)

* use prefetch function in ScheduleListItem and refactor

* modify app router trpc provider to match the original configs

* revert unneeded change

* move trpc provider up and do refactors

* remove log

* add comment

* fix: Some typing style fixes

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
This commit is contained in:
Benny Joo
2024-12-13 20:26:51 -05:00
committed by GitHub
co-authored by Alex van Andel
parent d12fc78fea
commit f72748b6f2
3 changed files with 74 additions and 41 deletions
+41 -11
View File
@@ -1,14 +1,18 @@
import { type DehydratedState, QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { HydrateClient } from "app/_trpc/HydrateClient";
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { trpc } from "app/_trpc/client";
import { useState } from "react";
import superjson from "superjson";
import { httpBatchLink, httpLink, loggerLink, splitLink } from "@calcom/trpc/client";
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.
@@ -32,16 +36,44 @@ const resolveEndpoint = (links: any) => {
};
};
export const TrpcProvider: React.FC<{ children: React.ReactNode; dehydratedState?: DehydratedState }> = ({
children,
dehydratedState,
}) => {
const isTRPCClientError = (cause: unknown): cause is TRPCClientError<AppRouter> => {
return cause instanceof TRPCClientError;
};
type Props = {
children: React.ReactNode;
};
export const TrpcProvider = ({ children }: Props) => {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: { queries: { staleTime: 5000 } },
// 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"
@@ -94,9 +126,7 @@ export const TrpcProvider: React.FC<{ children: React.ReactNode; dehydratedState
return (
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
{dehydratedState ? <HydrateClient state={dehydratedState}>{children}</HydrateClient> : children}
</QueryClientProvider>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
</trpc.Provider>
);
};
+2 -1
View File
@@ -1,3 +1,4 @@
import { TrpcProvider } from "app/_trpc/trpc-provider";
import { dir } from "i18next";
import { Inter } from "next/font/google";
import localFont from "next/font/local";
@@ -112,7 +113,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
}}
/>
)}
{children}
<TrpcProvider>{children}</TrpcProvider>
</body>
</html>
);
+31 -29
View File
@@ -1,5 +1,5 @@
import { TooltipProvider } from "@radix-ui/react-tooltip";
import { TrpcProvider } from "app/_trpc/trpc-provider";
import { HydrateClient } from "app/_trpc/HydrateClient";
import { dir } from "i18next";
import type { Session } from "next-auth";
import { SessionProvider, useSession } from "next-auth/react";
@@ -265,43 +265,45 @@ function OrgBrandProvider({ children }: { children: React.ReactNode }) {
const AppProviders = (props: PageWrapperProps) => {
// No need to have intercom on public pages - Good for Page Performance
const isBookingPage = useIsBookingPage();
const RemainingProviders = (
<TrpcProvider dehydratedState={props.dehydratedState}>
<EventCollectionProvider options={{ apiPath: "/api/collect-events" }}>
<SessionProvider>
<CustomI18nextProvider i18n={props.i18n}>
<TooltipProvider>
{/* color-scheme makes background:transparent not work which is required by embed. We need to ensure next-theme adds color-scheme to `body` instead of `html`(https://github.com/pacocoursey/next-themes/blob/main/src/index.tsx#L74). Once that's done we can enable color-scheme support */}
<CalcomThemeProvider
themeBasis={props.themeBasis}
nonce={props.nonce}
isThemeSupported={/* undefined gets treated as true */ props.isThemeSupported}
isBookingPage={props.isBookingPage || isBookingPage}>
<FeatureFlagsProvider>
<OrgBrandProvider>
{/* @ts-expect-error FIXME remove this comment when upgrading typescript to v5 */}
<CacheProvider>
<MetaProvider>{props.children}</MetaProvider>
</CacheProvider>
</OrgBrandProvider>
</FeatureFlagsProvider>
</CalcomThemeProvider>
</TooltipProvider>
</CustomI18nextProvider>
</SessionProvider>
</EventCollectionProvider>
</TrpcProvider>
<EventCollectionProvider options={{ apiPath: "/api/collect-events" }}>
<SessionProvider>
<CustomI18nextProvider i18n={props.i18n}>
<TooltipProvider>
{/* color-scheme makes background:transparent not work which is required by embed. We need to ensure next-theme adds color-scheme to `body` instead of `html`(https://github.com/pacocoursey/next-themes/blob/main/src/index.tsx#L74). Once that's done we can enable color-scheme support */}
<CalcomThemeProvider
themeBasis={props.themeBasis}
nonce={props.nonce}
isThemeSupported={/* undefined gets treated as true */ props.isThemeSupported}
isBookingPage={props.isBookingPage || isBookingPage}>
<FeatureFlagsProvider>
<OrgBrandProvider>
{/* @ts-expect-error FIXME remove this comment when upgrading typescript to v5 */}
<CacheProvider>
<MetaProvider>{props.children}</MetaProvider>
</CacheProvider>
</OrgBrandProvider>
</FeatureFlagsProvider>
</CalcomThemeProvider>
</TooltipProvider>
</CustomI18nextProvider>
</SessionProvider>
</EventCollectionProvider>
);
const Hydrated = props.dehydratedState ? (
<HydrateClient state={props.dehydratedState}>{RemainingProviders}</HydrateClient>
) : (
RemainingProviders
);
if (isBookingPage) {
return RemainingProviders;
return Hydrated;
}
return (
<DynamicHelpscoutProvider>
<DynamicIntercomProvider>
<DynamicPostHogProvider>{RemainingProviders}</DynamicPostHogProvider>
<DynamicPostHogProvider>{Hydrated}</DynamicPostHogProvider>
</DynamicIntercomProvider>
</DynamicHelpscoutProvider>
);