diff --git a/.changeset/healthy-tools-remember.md b/.changeset/healthy-tools-remember.md new file mode 100644 index 0000000..9583c71 --- /dev/null +++ b/.changeset/healthy-tools-remember.md @@ -0,0 +1,7 @@ +--- +'@ssrx/plugin-tanstack-query': minor +'@ssrx/trpc-react-query': minor +--- + +Per tanstack query recommendations, stop using the suspense: true option by default. Thus you must change your +.useQuery() instances to .useSuspenseQuery() if you would like to preserve the SSR data fetching. diff --git a/examples/react-router-kitchen-sink/src/api/trpc/articles.ts b/examples/react-router-kitchen-sink/src/api/trpc/articles.ts index a7507e0..b2fdb26 100644 --- a/examples/react-router-kitchen-sink/src/api/trpc/articles.ts +++ b/examples/react-router-kitchen-sink/src/api/trpc/articles.ts @@ -2,9 +2,9 @@ import { TRPCError } from '@trpc/server'; import { desc, eq, or } from 'drizzle-orm'; import { object, omit, parse, partial, pick } from 'valibot'; -import { sleep } from '~/utils.ts'; import { createDbId } from '~/api/db/ids.ts'; import { articles, insertArticleSchema, selectArticleSchema } from '~/api/db/schema/index.ts'; +import { sleep } from '~/utils.ts'; import { protectedProcedure, publicProcedure, router } from './trpc.ts'; diff --git a/examples/react-router-kitchen-sink/src/components/article-action-bar.tsx b/examples/react-router-kitchen-sink/src/components/article-action-bar.tsx index 8320bb3..4407d4a 100644 --- a/examples/react-router-kitchen-sink/src/components/article-action-bar.tsx +++ b/examples/react-router-kitchen-sink/src/components/article-action-bar.tsx @@ -18,7 +18,7 @@ export const ActionBar = ({ trailingActions?: React.ReactNode; }) => { const navigate = useNavigate(); - const { data } = ctx.trpc.auth.me.useQuery(); + const { data } = ctx.trpc.auth.me.useSuspenseQuery(); if (!data) return null; return ( diff --git a/examples/react-router-kitchen-sink/src/components/query-boundary.tsx b/examples/react-router-kitchen-sink/src/components/query-boundary.tsx index 3586c0a..454f927 100644 --- a/examples/react-router-kitchen-sink/src/components/query-boundary.tsx +++ b/examples/react-router-kitchen-sink/src/components/query-boundary.tsx @@ -1,9 +1,9 @@ -import type { UseQueryResult } from '@tanstack/react-query'; +import type { UseQueryResult, UseSuspenseQueryResult } from '@tanstack/react-query'; import { type ReactNode, Suspense, useCallback } from 'react'; import { ErrorBoundary, type FallbackProps } from 'react-error-boundary'; export type QueryBoundaryProps = { - query: () => UseQueryResult; + query: () => UseQueryResult | UseSuspenseQueryResult; /** * Triggered when the data is initially loading. diff --git a/examples/react-router-kitchen-sink/src/routes/articles.$articleId.edit.tsx b/examples/react-router-kitchen-sink/src/routes/articles.$articleId.edit.tsx index 7e76a78..c7ce868 100644 --- a/examples/react-router-kitchen-sink/src/routes/articles.$articleId.edit.tsx +++ b/examples/react-router-kitchen-sink/src/routes/articles.$articleId.edit.tsx @@ -23,7 +23,7 @@ export function Component() { return ( Loading article (with simulated latency)...} - query={() => ctx.trpc.articles.byId.useQuery({ id: articleId! })} + query={() => ctx.trpc.articles.byId.useSuspenseQuery({ id: articleId! })} > {data =>
} diff --git a/examples/react-router-kitchen-sink/src/routes/articles.$articleId.tsx b/examples/react-router-kitchen-sink/src/routes/articles.$articleId.tsx index 180c51b..332609d 100644 --- a/examples/react-router-kitchen-sink/src/routes/articles.$articleId.tsx +++ b/examples/react-router-kitchen-sink/src/routes/articles.$articleId.tsx @@ -18,7 +18,7 @@ export function Component() { return ( Loading article (with simulated latency)...} - query={() => ctx.trpc.articles.byId.useQuery({ id: articleId! })} + query={() => ctx.trpc.articles.byId.useSuspenseQuery({ id: articleId! })} > {data =>
} diff --git a/examples/react-router-kitchen-sink/src/routes/articles.tsx b/examples/react-router-kitchen-sink/src/routes/articles.tsx index 7e7980c..8b199dc 100644 --- a/examples/react-router-kitchen-sink/src/routes/articles.tsx +++ b/examples/react-router-kitchen-sink/src/routes/articles.tsx @@ -18,7 +18,7 @@ export function Component() {
ctx.trpc.articles.list.useQuery()} + query={() => ctx.trpc.articles.list.useSuspenseQuery()} loadingFallback={
Loading articles (with simulated latency)...
} diff --git a/examples/react-router-kitchen-sink/src/routes/root.tsx b/examples/react-router-kitchen-sink/src/routes/root.tsx index ceb6293..19ea8dc 100644 --- a/examples/react-router-kitchen-sink/src/routes/root.tsx +++ b/examples/react-router-kitchen-sink/src/routes/root.tsx @@ -73,7 +73,7 @@ export function Component() { const AddArticleButton = ({ onSuccess }: { onSuccess?: (article: RouterOutputs['articles']['create']) => void }) => { const { toast } = useToast(); - const { data } = ctx.trpc.auth.me.useQuery(); + const { data } = ctx.trpc.auth.me.useSuspenseQuery(); const isLoggedIn = !!data; const mut = ctx.trpc.articles.create.useMutation({ @@ -125,7 +125,7 @@ const AddArticleButton = ({ onSuccess }: { onSuccess?: (article: RouterOutputs[' }; const LoginButton = () => { - const { data, isLoading } = ctx.trpc.auth.me.useQuery(); + const { data, isLoading } = ctx.trpc.auth.me.useSuspenseQuery(); const login = ctx.trpc.auth.login.useMutation({ onSuccess: () => { // invalidate the entire query cache on login/logout @@ -156,7 +156,7 @@ const LoginButton = () => { }; const LogoutButton = () => { - const { data } = ctx.trpc.auth.me.useQuery(); + const { data } = ctx.trpc.auth.me.useSuspenseQuery(); const logout = ctx.trpc.auth.logout.useMutation({ onSuccess: () => { // invalidate the entire query cache on login/logout diff --git a/examples/react-router-kitchen-sink/src/routes/wait.tsx b/examples/react-router-kitchen-sink/src/routes/wait.tsx index 427896d..b44a17f 100644 --- a/examples/react-router-kitchen-sink/src/routes/wait.tsx +++ b/examples/react-router-kitchen-sink/src/routes/wait.tsx @@ -1,4 +1,4 @@ -import { useQuery } from '@tanstack/react-query'; +import { useSuspenseQuery } from '@tanstack/react-query'; import { Suspense } from 'react'; import { Counter } from '~/components/counter.tsx'; @@ -66,7 +66,7 @@ export function Component() { } function useWaitQuery(props: { wait: number; deferStream?: boolean; simulateErrorOnServer?: boolean }) { - const query = useQuery({ + const query = useSuspenseQuery({ queryKey: ['wait', props.wait, props.simulateErrorOnServer], meta: { deferStream: props.deferStream }, queryFn: async () => { diff --git a/examples/remix-vite/app/components/article-action-bar.tsx b/examples/remix-vite/app/components/article-action-bar.tsx index 1b76bdd..e63648a 100644 --- a/examples/remix-vite/app/components/article-action-bar.tsx +++ b/examples/remix-vite/app/components/article-action-bar.tsx @@ -18,7 +18,7 @@ export const ActionBar = ({ trailingActions?: React.ReactNode; }) => { const navigate = useNavigate(); - const { data } = ctx.trpc.auth.me.useQuery(); + const { data } = ctx.trpc.auth.me.useSuspenseQuery(); if (!data) return null; return ( diff --git a/examples/remix-vite/app/components/query-boundary.tsx b/examples/remix-vite/app/components/query-boundary.tsx index bbf705e..e5e6146 100644 --- a/examples/remix-vite/app/components/query-boundary.tsx +++ b/examples/remix-vite/app/components/query-boundary.tsx @@ -1,9 +1,9 @@ -import type { UseQueryResult } from '@tanstack/react-query'; +import type { UseQueryResult, UseSuspenseQueryResult } from '@tanstack/react-query'; import { type ReactNode, Suspense, useCallback } from 'react'; import { ErrorBoundary, type FallbackProps } from 'react-error-boundary'; export type QueryBoundaryProps = { - query: () => UseQueryResult; + query: () => UseQueryResult | UseSuspenseQueryResult; /** * Triggered when the data is initially loading. diff --git a/examples/remix-vite/app/root.tsx b/examples/remix-vite/app/root.tsx index aefb1c6..8304beb 100644 --- a/examples/remix-vite/app/root.tsx +++ b/examples/remix-vite/app/root.tsx @@ -73,7 +73,7 @@ const AppContainer = () => { const AddArticleButton = ({ onSuccess }: { onSuccess?: (article: RouterOutputs['articles']['create']) => void }) => { const { toast } = useToast(); - const { data } = ctx.trpc.auth.me.useQuery(); + const { data } = ctx.trpc.auth.me.useSuspenseQuery(); const isLoggedIn = !!data; const mut = ctx.trpc.articles.create.useMutation({ @@ -125,7 +125,7 @@ const AddArticleButton = ({ onSuccess }: { onSuccess?: (article: RouterOutputs[' }; const LoginButton = () => { - const { data, isLoading } = ctx.trpc.auth.me.useQuery(); + const { data, isLoading } = ctx.trpc.auth.me.useSuspenseQuery(); const login = ctx.trpc.auth.login.useMutation({ onSuccess: () => { // invalidate the entire query cache on login/logout @@ -156,7 +156,7 @@ const LoginButton = () => { }; const LogoutButton = () => { - const { data } = ctx.trpc.auth.me.useQuery(); + const { data } = ctx.trpc.auth.me.useSuspenseQuery(); const logout = ctx.trpc.auth.logout.useMutation({ onSuccess: () => { // invalidate the entire query cache on login/logout diff --git a/examples/remix-vite/app/routes/articles.$articleId.edit.tsx b/examples/remix-vite/app/routes/articles.$articleId.edit.tsx index b89da3e..d8cd2f1 100644 --- a/examples/remix-vite/app/routes/articles.$articleId.edit.tsx +++ b/examples/remix-vite/app/routes/articles.$articleId.edit.tsx @@ -16,7 +16,7 @@ export default function Component() { return ( Loading article (with simulated latency)...
} - query={() => ctx.trpc.articles.byId.useQuery({ id: articleId! })} + query={() => ctx.trpc.articles.byId.useSuspenseQuery({ id: articleId! })} > {data =>
} diff --git a/examples/remix-vite/app/routes/articles.$articleId.tsx b/examples/remix-vite/app/routes/articles.$articleId.tsx index 1044503..62e3b2f 100644 --- a/examples/remix-vite/app/routes/articles.$articleId.tsx +++ b/examples/remix-vite/app/routes/articles.$articleId.tsx @@ -12,7 +12,7 @@ export default function ArticlePage() {
Loading article (with simulated latency)...
} - query={() => ctx.trpc.articles.byId.useQuery({ id: articleId! })} + query={() => ctx.trpc.articles.byId.useSuspenseQuery({ id: articleId! })} > {data =>
} diff --git a/examples/remix-vite/app/routes/articles.tsx b/examples/remix-vite/app/routes/articles.tsx index 7e6db9d..ffbba33 100644 --- a/examples/remix-vite/app/routes/articles.tsx +++ b/examples/remix-vite/app/routes/articles.tsx @@ -15,7 +15,7 @@ export default function Articles() {
ctx.trpc.articles.list.useQuery()} + query={() => ctx.trpc.articles.list.useSuspenseQuery()} loadingFallback={
Loading articles (with simulated latency)...
} diff --git a/packages/plugin-tanstack-query/src/query-client.ts b/packages/plugin-tanstack-query/src/query-client.ts index 7394ecd..9acaf5c 100644 --- a/packages/plugin-tanstack-query/src/query-client.ts +++ b/packages/plugin-tanstack-query/src/query-client.ts @@ -1,4 +1,3 @@ -import { deepmerge } from '@ssrx/renderer'; import { QueryCache, QueryClient, type QueryClientConfig } from '@tanstack/query-core'; type CreateQueryClientOpts = { @@ -24,14 +23,7 @@ export const createQueryClient = ({ trackedQueries, blockingQueries, clientConfi const queryClient: QueryClient = new QueryClient({ queryCache, - defaultOptions: deepmerge( - { - queries: { - suspense: true, - }, - }, - clientConfig?.defaultOptions || {}, - ), + defaultOptions: clientConfig?.defaultOptions || {}, }); if (trackedQueries && import.meta.env.SSR) { diff --git a/packages/trpc-react-query/src/createHooksInternal.tsx b/packages/trpc-react-query/src/createHooksInternal.tsx index 090544a..ccff659 100644 --- a/packages/trpc-react-query/src/createHooksInternal.tsx +++ b/packages/trpc-react-query/src/createHooksInternal.tsx @@ -12,6 +12,8 @@ import { type UseMutationResult, useQuery as __useQuery, type UseQueryResult, + useSuspenseQuery as __useSuspenseQuery, + type UseSuspenseQueryResult, } from '@tanstack/react-query'; import { type TRPCClientErrorLike } from '@trpc/client'; import type { @@ -33,6 +35,7 @@ import type { UseTRPCInfiniteQueryOptions, UseTRPCMutationOptions, UseTRPCQueryOptions, + UseTRPCSuspenseQueryOptions, } from './types.ts'; export interface UseTRPCSubscriptionOptions { @@ -67,6 +70,11 @@ type inferProcedures = { */ export type UseTRPCQueryResult = UseQueryResult; +/** + * @internal + */ +export type UseTRPCSuspenseQueryResult = UseSuspenseQueryResult; + /** * @internal */ @@ -183,7 +191,27 @@ export function createHooksInternal(config: CreateTRP ...(opts as any), }, queryClient, - ) as UseTRPCQueryResult; + ) satisfies UseTRPCQueryResult; + } + + function useSuspenseQuery< + TPath extends keyof TQueryValues & string, + TQueryFnData = TQueryValues[TPath]['output'], + TData = TQueryValues[TPath]['output'], + >( + pathAndInput: [path: TPath, ...args: inferHandlerInput], + opts?: UseTRPCSuspenseQueryOptions, + ): UseTRPCSuspenseQueryResult { + return __useSuspenseQuery( + { + queryKey: getArrayQueryKey(pathAndInput, 'query'), + queryFn: () => { + return (config.client as any).query(...getClientArgs(pathAndInput, opts)); + }, + ...(opts as any), + }, + queryClient, + ) satisfies UseTRPCSuspenseQueryResult; } function useMutation( @@ -212,7 +240,12 @@ export function createHooksInternal(config: CreateTRP }, }, queryClient, - ) as UseTRPCMutationResult; + ) satisfies UseTRPCMutationResult< + TMutationValues[TPath]['output'], + TError, + TMutationValues[TPath]['input'], + TContext + >; } /** @@ -287,7 +320,7 @@ export function createHooksInternal(config: CreateTRP ...(opts as any), }, queryClient, - ) as UseTRPCInfiniteQueryResult; + ) satisfies UseTRPCInfiniteQueryResult; } return { @@ -301,6 +334,7 @@ export function createHooksInternal(config: CreateTRP useMutation, useSubscription, useInfiniteQuery, + useSuspenseQuery, }; } diff --git a/packages/trpc-react-query/src/createTRPCReact.ts b/packages/trpc-react-query/src/createTRPCReact.ts index d5e2a43..d8aba79 100644 --- a/packages/trpc-react-query/src/createTRPCReact.ts +++ b/packages/trpc-react-query/src/createTRPCReact.ts @@ -28,6 +28,7 @@ import { type UseTRPCMutationResult, type UseTRPCQueryResult, type UseTRPCSubscriptionOptions, + type UseTRPCSuspenseQueryResult, } from './createHooksInternal.tsx'; import { createSolidProxyDecoration } from './decorationProxy.ts'; import type { @@ -36,6 +37,7 @@ import type { UseTRPCInfiniteQueryOptions, UseTRPCMutationOptions, UseTRPCQueryOptions, + UseTRPCSuspenseQueryOptions, } from './types.ts'; /** @@ -58,6 +60,17 @@ export type DecorateProcedure< >, ) => UseTRPCQueryResult>; + useSuspenseQuery: , TData = inferProcedureOutput>( + input: inferProcedureInput, + opts?: UseTRPCSuspenseQueryOptions< + TPath, + inferProcedureInput, + TOutput, + TData, + TRPCClientErrorLike + >, + ) => UseTRPCSuspenseQueryResult>; + fetchQuery>( input: inferProcedureInput, opts?: TRPCFetchQueryOptions, TRPCClientErrorLike, TOutput>, diff --git a/packages/trpc-react-query/src/types.ts b/packages/trpc-react-query/src/types.ts index f046534..0bac1b4 100644 --- a/packages/trpc-react-query/src/types.ts +++ b/packages/trpc-react-query/src/types.ts @@ -5,8 +5,9 @@ import type { MutationOptions, QueryClient, QueryKey, - QueryObserverOptions, UseInfiniteQueryOptions, + UseQueryOptions, + UseSuspenseQueryOptions, } from '@tanstack/react-query'; import type { TRPCRequestOptions, TRPCUntypedClient } from '@trpc/client'; import type { AnyRouter, MaybePromise } from '@trpc/server'; @@ -57,7 +58,7 @@ export type CreateQueryOptions< TError = unknown, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, -> = OmitUseless>; +> = OmitUseless>; export type CreateInfiniteQueryOptions< TQueryFnData = unknown, @@ -66,6 +67,13 @@ export type CreateInfiniteQueryOptions< TQueryKey extends QueryKey = QueryKey, > = OmitUseless>; +export type CreateSuspenseQueryOptions< + TQueryFnData = unknown, + TError = unknown, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +> = OmitUseless>; + export type CreateMutationOptions = OmitUseless< MutationOptions >; @@ -78,6 +86,10 @@ export interface UseTRPCInfiniteQueryOptions export interface UseTRPCQueryOptions extends CreateQueryOptions {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface UseTRPCSuspenseQueryOptions + extends CreateSuspenseQueryOptions {} + export interface UseTRPCMutationOptions extends CreateMutationOptions {}