Skip to content

Commit 6f258fc

Browse files
committed
feat(trpc): use new tanstack react query integration for api-connection
tRPC recently released a new client for calling tRPC APIs using TanStack React Query: https://trpc.io/blog/introducing-tanstack-react-query-client In a nutshell, it's a lot simpler as it doesn't try to wrap all of TanStack React Query's functionality. For more info, please refer to the docs here: https://trpc.io/docs/client/tanstack-react-query/usage
1 parent 2aab6c0 commit 6f258fc

File tree

8 files changed

+120
-104
lines changed

8 files changed

+120
-104
lines changed

docs/src/content/docs/reference/trpc.md

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ Additionally, it:
492492

493493
1. Installs required dependencies:
494494
- @trpc/client
495-
- @trpc/react-query
495+
- @trpc/tanstack-react-query
496496
- @tanstack/react-query
497497
- aws4fetch (if using IAM auth)
498498

@@ -503,16 +503,17 @@ Additionally, it:
503503
The generator provides a `use<ApiName>` hook that gives you access to the type-safe tRPC client:
504504

505505
```tsx
506+
import { useQuery, useMutation } from '@tanstack/react-query';
506507
import { useMyApi } from './hooks/useMyApi';
507508

508509
function MyComponent() {
509510
const trpc = useMyApi();
510511

511512
// Example query
512-
const { data, isLoading, error } = trpc.users.list.useQuery();
513+
const { data, isLoading, error } = useQuery(trpc.users.list.queryOptions());
513514

514515
// Example mutation
515-
const mutation = trpc.users.create.useMutation();
516+
const mutation = useMutation(trpc.users.create.mutationOptions());
516517

517518
const handleCreate = () => {
518519
mutation.mutate({
@@ -541,7 +542,7 @@ The integration includes built-in error handling that properly processes tRPC er
541542
function MyComponent() {
542543
const trpc = useMyApi();
543544

544-
const { data, error } = trpc.users.list.useQuery();
545+
const { data, error } = useQuery(trpc.users.list.queryOptions());
545546

546547
if (error) {
547548
return (
@@ -573,7 +574,7 @@ Always handle loading and error states for a better user experience:
573574
function UserList() {
574575
const trpc = useMyApi();
575576

576-
const users = trpc.users.list.useQuery();
577+
const users = useQuery(trpc.users.list.queryOptions());
577578

578579
if (users.isLoading) {
579580
return <LoadingSpinner />;
@@ -598,29 +599,33 @@ function UserList() {
598599
Use optimistic updates for a better user experience:
599600

600601
```tsx
602+
import { useQueryClient, useQuery, useMutation } from '@tanstack/react-query';
603+
601604
function UserList() {
602605
const trpc = useMyApi();
603-
const users = trpc.users.list.useQuery();
604-
const utils = trpc.useUtils();
605-
606-
const deleteMutation = trpc.users.delete.useMutation({
607-
onMutate: async (userId) => {
608-
// Cancel outgoing fetches
609-
await utils.users.list.cancel();
610-
611-
// Get snapshot of current data
612-
const previousUsers = utils.users.list.getData();
613-
614-
// Optimistically remove the user
615-
utils.users.list.setData(undefined, (old) => old?.filter((user) => user.id !== userId));
616-
617-
return { previousUsers };
618-
},
619-
onError: (err, userId, context) => {
620-
// Restore previous data on error
621-
utils.users.list.setData(undefined, context?.previousUsers);
622-
},
623-
});
606+
const users = useQuery(trpc.users.list.queryOptions());
607+
const queryClient = useQueryClient();
608+
609+
const deleteMutation = useMutation(
610+
trpc.users.delete.mutationOptions({
611+
onMutate: async (userId) => {
612+
// Cancel outgoing fetches
613+
await queryClient.cancelQueries(trpc.users.list.queryFilter());
614+
615+
// Get snapshot of current data
616+
const previousUsers = queryClient.getQueryData(trpc.users.list.queryKey());
617+
618+
// Optimistically remove the user
619+
queryClient.setQueryData(trpc.users.list.queryKey(), (old) => old?.filter((user) => user.id !== userId));
620+
621+
return { previousUsers };
622+
},
623+
onError: (err, userId, context) => {
624+
// Restore previous data on error
625+
queryClient.setQueryData(trpc.users.list.queryKey(), context?.previousUsers);
626+
},
627+
}),
628+
);
624629

625630
return (
626631
<ul>
@@ -642,11 +647,12 @@ Prefetch data for better performance:
642647
```tsx
643648
function UserList() {
644649
const trpc = useMyApi();
645-
const users = trpc.users.list.useQuery();
650+
const users = useQuery(trpc.users.list.queryOptions());
651+
const queryClient = useQueryClient();
646652

647653
// Prefetch user details on hover
648654
const prefetchUser = async (userId: string) => {
649-
await trpc.users.getById.usePrefetchQuery(userId);
655+
await queryClient.prefetchQuery(trpc.users.getById.queryOptions(userId));
650656
};
651657

652658
return (
@@ -669,11 +675,13 @@ Handle pagination with infinite queries:
669675
function UserList() {
670676
const trpc = useMyApi();
671677

672-
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = trpc.users.list.useInfiniteQuery(
673-
{ limit: 10 },
674-
{
675-
getNextPageParam: (lastPage) => lastPage.nextCursor,
676-
},
678+
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery(
679+
trpc.users.list.infiniteQueryOptions(
680+
{ limit: 10 },
681+
{
682+
getNextPageParam: (lastPage) => lastPage.nextCursor,
683+
},
684+
),
677685
);
678686

679687
return (
@@ -690,6 +698,8 @@ function UserList() {
690698
}
691699
```
692700

701+
It is important to note that infinite queries can only be used for procedures with an input property named `cursor`.
702+
693703
### Type Safety
694704

695705
The integration provides complete end-to-end type safety. Your IDE will provide full autocompletion and type checking for all your API calls:
@@ -711,3 +721,7 @@ function UserForm() {
711721
```
712722

713723
The types are automatically inferred from your backend's router and schema definitions, ensuring that any changes to your API are immediately reflected in your frontend code without the need to build.
724+
725+
### More Information
726+
727+
For more information, please refer to the [tRPC TanStack React Query documentation](https://trpc.io/docs/client/tanstack-react-query/usage).

packages/nx-plugin/src/trpc/react/README.md

Lines changed: 46 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
2323
root.render(
2424
<StrictMode>
2525
<App />
26-
</StrictMode>
26+
</StrictMode>,
2727
);
2828
```
2929

@@ -53,13 +53,13 @@ Then add tRPC to your React application:
5353
Add tRPC to your React application:
5454

5555
```bash
56-
nx g @aws/nx-plugin:trpc#react --frontendProjectName=my-app --backendProjectName=my-api --auth=IAM
56+
nx g @aws/nx-plugin:trpc#react-connection --frontendProjectName=my-app --backendProjectName=my-api --auth=IAM
5757
```
5858

5959
You can also perform a dry-run to see what files would be generated without actually creating them:
6060

6161
```bash
62-
nx g @aws/nx-plugin:trpc#react --frontendProjectName=my-app --backendProjectName=my-api --auth=IAM --dry-run
62+
nx g @aws/nx-plugin:trpc#react-connection --frontendProjectName=my-app --backendProjectName=my-api --auth=IAM --dry-run
6363
```
6464

6565
Both methods will add tRPC client integration to your React application with all the necessary configuration.
@@ -95,7 +95,7 @@ Additionally, it:
9595

9696
1. Installs required dependencies:
9797
- @trpc/client
98-
- @trpc/react-query
98+
- @trpc/tanstack-react-query
9999
- @tanstack/react-query
100100
- aws4fetch (if using IAM auth)
101101

@@ -106,13 +106,14 @@ Additionally, it:
106106
The generator provides a `use<ApiName>` hook that gives you access to the type-safe tRPC client:
107107

108108
```tsx
109+
import { useQuery } from '@tanstack/react-query';
109110
import { useMyApi } from './hooks/useMyApi';
110111

111112
function MyComponent() {
112113
const trpc = useMyApi();
113114

114115
// Example query
115-
const { data, isLoading } = trpc.users.list.useQuery();
116+
const { data, isLoading } = useQuery(trpc.users.list.queryOptions());
116117

117118
// Example mutation
118119
const mutation = trpc.users.create.useMutation();
@@ -138,7 +139,7 @@ The integration includes built-in error handling that properly processes tRPC er
138139
function MyComponent() {
139140
const trpc = useTrpc();
140141

141-
const { data, error } = trpc.users.list.useQuery();
142+
const { data, error } = useQuery(trpc.users.list.queryOptions());
142143

143144
if (error) {
144145
return (
@@ -162,7 +163,8 @@ Always handle loading states for better user experience:
162163

163164
```tsx
164165
function UserList() {
165-
const { users } = useUsers();
166+
const trpc = useTrpc();
167+
const users = useQuery(trpc.users.list.queryOptions());
166168

167169
if (users.isLoading) {
168170
return <LoadingSpinner />;
@@ -187,28 +189,32 @@ function UserList() {
187189
Use optimistic updates for better user experience:
188190

189191
```tsx
190-
function UserList() {
191-
const trpc = useUsers();
192-
const utils = trpc.useUtils();
193-
194-
const deleteMutation = trpc.users.delete.useMutation({
195-
onMutate: async (userId) => {
196-
// Cancel outgoing fetches
197-
await utils.users.list.cancel();
192+
import { useQueryClient } from '@tanstack/react-query';
198193

199-
// Get snapshot of current data
200-
const previousUsers = utils.users.list.getData();
201-
202-
// Optimistically remove the user
203-
utils.users.list.setData(undefined, (old) => old?.filter((user) => user.id !== userId));
194+
function UserList() {
195+
const queryClient = useQueryClient();
196+
const trpc = useTrpc();
204197

205-
return { previousUsers };
206-
},
207-
onError: (err, userId, context) => {
208-
// Restore previous data on error
209-
utils.users.list.setData(undefined, context?.previousUsers);
210-
},
211-
});
198+
const deleteMutation = useMutation(
199+
trpc.users.delete.mutationOptions({
200+
onMutate: async (userId) => {
201+
// Cancel outgoing fetches
202+
await queryClient.cancelQueries(trpc.users.list.queryFilter());
203+
204+
// Get snapshot of current data
205+
const previousUsers = queryClient.getQueryData(trpc.users.list.queryKey());
206+
207+
// Optimistically remove the user
208+
queryClient.setQueryData(trpc.users.list.queryKey(), (old) => old?.filter((user) => user.id !== userId));
209+
210+
return { previousUsers };
211+
},
212+
onError: (err, userId, context) => {
213+
// Restore previous data on error
214+
queryClient.setQueryData(trpc.users.list.queryKey(), context?.previousUsers);
215+
},
216+
}),
217+
);
212218

213219
return (
214220
<ul>
@@ -229,11 +235,12 @@ Prefetch data for better performance:
229235

230236
```tsx
231237
function UserList() {
232-
const trpc = useUsers();
238+
const queryClient = useQueryClient();
239+
const trpc = useTrpc();
233240

234241
// Prefetch user details on hover
235242
const prefetchUser = async (userId: string) => {
236-
await trpc.users.getById.usePrefetchQuery(userId);
243+
await queryClient.prefetchQuery(trpc.users.getById.queryOptions(userId));
237244
};
238245

239246
return (
@@ -254,13 +261,15 @@ Handle pagination with infinite queries:
254261

255262
```tsx
256263
function UserList() {
257-
const trpc = useUsers();
264+
const trpc = useTrpc();
258265

259-
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = trpc.users.list.useInfiniteQuery(
260-
{ limit: 10 },
261-
{
262-
getNextPageParam: (lastPage) => lastPage.nextCursor,
263-
}
266+
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery(
267+
trpc.users.list.infiniteQueryOptions(
268+
{ limit: 10 },
269+
{
270+
getNextPageParam: (lastPage) => lastPage.nextCursor,
271+
},
272+
),
264273
);
265274

266275
return (
@@ -283,10 +292,10 @@ The integration provides complete end-to-end type safety. Your IDE will provide
283292

284293
```tsx
285294
function UserForm() {
286-
const trpc = useUsers();
295+
const trpc = useTrpc();
287296

288297
// ✅ Input is fully typed
289-
const createUser = trpc.users.create.useMutation();
298+
const createUser = useMutation(trpc.users.create.mutationOptions());
290299

291300
const handleSubmit = (data: CreateUserInput) => {
292301
// ✅ Type error if input doesn't match schema

packages/nx-plugin/src/trpc/react/__snapshots__/generator.spec.ts.snap

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@
22

33
exports[`trpc react generator > should generate trpc react files > TrpcClients-IsolatedTrpcProvider.tsx 1`] = `
44
"import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
5-
import { createTRPCReact } from '@trpc/react-query';
5+
import { createTRPCContext } from '@trpc/tanstack-react-query';
66
import {
77
httpBatchLink,
88
httpLink,
99
HTTPBatchLinkOptions,
1010
HTTPLinkOptions,
1111
splitLink,
12+
createTRPCClient,
1213
} from '@trpc/client';
13-
import { useState, FC, createContext, useMemo, PropsWithChildren } from 'react';
14+
import { useState, FC, useMemo, PropsWithChildren } from 'react';
1415
1516
import { AnyTRPCRouter } from '@trpc/server';
1617
@@ -22,10 +23,7 @@ export const createIsolatedTrpcClientProvider = <
2223
TAppRouter extends AnyTRPCRouter,
2324
TContext,
2425
>() => {
25-
const isolatedTrpcContext = createContext(null as any);
26-
const trpc = createTRPCReact<TAppRouter, TContext>({
27-
context: isolatedTrpcContext,
28-
});
26+
const { TRPCProvider, useTRPC } = createTRPCContext<TAppRouter>();
2927
3028
const IsolatedTrpcClientProvider: FC<IsolatedTrpcClientProviderProps> = ({
3129
apiUrl,
@@ -38,8 +36,7 @@ export const createIsolatedTrpcClientProvider = <
3836
url: apiUrl,
3937
};
4038
41-
// @ts-expect-error type errors due to unknown concrete AppRouter
42-
return trpc.createClient({
39+
return createTRPCClient({
4340
links: [
4441
splitLink({
4542
condition(op) {
@@ -53,18 +50,16 @@ export const createIsolatedTrpcClientProvider = <
5350
}, [apiUrl]);
5451
5552
return (
56-
// @ts-expect-error type errors due to unknown concrete AppRouter
57-
<trpc.Provider client={trpcClient} queryClient={queryClient}>
53+
<TRPCProvider trpcClient={trpcClient} queryClient={queryClient}>
5854
<QueryClientProvider client={queryClient}>
5955
{children}
6056
</QueryClientProvider>
61-
{/* @ts-expect-error type errors due to unknown concrete AppRouter */}
62-
</trpc.Provider>
57+
</TRPCProvider>
6358
);
6459
};
6560
6661
return {
67-
trpc,
62+
useTRPC,
6863
Provider: IsolatedTrpcClientProvider,
6964
};
7065
};
@@ -111,7 +106,7 @@ export default TrpcClientProviders;
111106
exports[`trpc react generator > should generate trpc react files > useTestApi.tsx 1`] = `
112107
"import { TrpcApis } from '../components/TrpcClients';
113108
114-
export const useTestApi = () => TrpcApis.TestApi.trpc;
109+
export const useTestApi = () => TrpcApis.TestApi.useTRPC();
115110
"
116111
`;
117112

0 commit comments

Comments
 (0)