React đến tRPC
Nx Plugin cho AWS cung cấp một generator để nhanh chóng tích hợp tRPC API của bạn với một website React. Nó thiết lập tất cả cấu hình cần thiết để kết nối đến các backend tRPC của bạn, bao gồm hỗ trợ xác thực AWS IAM và Cognito cũng như xử lý lỗi phù hợp. Tích hợp này cung cấp type safety đầy đủ từ đầu đến cuối giữa frontend và (các) backend tRPC của bạn.
Điều kiện tiên quyết
Phần tiêu đề “Điều kiện tiên quyết”Trước khi sử dụng generator này, đảm bảo ứng dụng React của bạn có:
- Một file
main.tsxrender ứng dụng của bạn - Một phần tử JSX
<App/>nơi mà tRPC provider sẽ được tự động inject vào - Một tRPC API đang hoạt động (được tạo bằng tRPC API generator)
- Cognito Auth được thêm thông qua
ts#react-website-authgenerator nếu kết nối một API sử dụng Cognito hoặc IAM auth
Ví dụ về cấu trúc main.tsx bắt buộc
import { StrictMode } from 'react';import * as ReactDOM from 'react-dom/client';import App from './app/app';
const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement,);root.render( <StrictMode> <App /> </StrictMode>,);Cách sử dụng
Phần tiêu đề “Cách sử dụng”Chạy Generator
Phần tiêu đề “Chạy Generator”- Install the Nx Console VSCode Plugin if you haven't already
- Open the Nx Console in VSCode
- Click
Generate (UI)in the "Common Nx Commands" section - Search for
@aws/nx-plugin - connection - Fill in the required parameters
- Click
Generate
pnpm nx g @aws/nx-plugin:connectionyarn nx g @aws/nx-plugin:connectionnpx nx g @aws/nx-plugin:connectionbunx nx g @aws/nx-plugin:connectionYou can also perform a dry-run to see what files would be changed
pnpm nx g @aws/nx-plugin:connection --dry-runyarn nx g @aws/nx-plugin:connection --dry-runnpx nx g @aws/nx-plugin:connection --dry-runbunx nx g @aws/nx-plugin:connection --dry-runTùy chọn
Phần tiêu đề “Tùy chọn”| Parameter | Type | Default | Description |
|---|---|---|---|
| sourceProject Required | string | - | The source project |
| targetProject Required | string | - | The target project to connect to |
Kết quả của Generator
Phần tiêu đề “Kết quả của Generator”Generator tạo ra cấu trúc sau trong ứng dụng React của bạn:
Thư mụcsrc
Thư mụccomponents
- <ApiName>ClientProvider.tsx Thiết lập các tRPC client và bindings đến (các) schema backend của bạn. ApiName sẽ được giải quyết thành tên của API
- QueryClientProvider.tsx TanStack React Query client provider
Thư mụchooks
- useSigV4.tsx Hook để ký các HTTP request với SigV4 (chỉ IAM)
- use<ApiName>.tsx Một hook trả về tRPC options proxy để tích hợp với TanStack Query
- use<ApiName>Client.tsx Một hook trả về vanilla tRPC client cho các API call trực tiếp
Ngoài ra, nó cài đặt các dependencies bắt buộc:
@trpc/client@trpc/tanstack-react-query@tanstack/react-queryaws4fetch(nếu sử dụng IAM auth)event-source-polyfill(nếu sử dụng REST API, để hỗ trợ subscription)
Sử dụng Code được tạo ra
Phần tiêu đề “Sử dụng Code được tạo ra”Sử dụng tRPC Options Proxy Hook
Phần tiêu đề “Sử dụng tRPC Options Proxy Hook”Generator cung cấp một hook use<ApiName> trả về một tRPC options proxy để sử dụng với các hook TanStack Query như useQuery và useMutation:
import { useQuery, useMutation } from '@tanstack/react-query';import { useMyApi } from './hooks/useMyApi';
function MyComponent() { const trpc = useMyApi();
// Ví dụ query const { data, isLoading, error } = useQuery(trpc.users.list.queryOptions());
// Ví dụ mutation const mutation = useMutation(trpc.users.create.mutationOptions());
const handleCreate = () => { mutation.mutate({ name: 'John Doe', email: 'john@example.com', }); };
if (isLoading) return <div>Loading...</div>;
return ( <ul> {data.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> );}Sử dụng Vanilla tRPC Client
Phần tiêu đề “Sử dụng Vanilla tRPC Client”Hook use<ApiName>Client cung cấp truy cập đến vanilla tRPC client, hữu ích cho các API call mệnh lệnh và subscriptions:
import { useState } from 'react';import { useMyApiClient } from './hooks/useMyApi';
function MyComponent() { const client = useMyApiClient();
const handleClick = async () => { const result = await client.echo.query({ message: 'Hello!' }); console.log(result);
const mutationResult = await client.users.create.mutate({ name: 'Jane' }); console.log(mutationResult); };
return <button onClick={handleClick}>Call API</button>;}Xử lý lỗi
Phần tiêu đề “Xử lý lỗi”Tích hợp này bao gồm xử lý lỗi tích hợp sẵn để xử lý đúng các lỗi tRPC:
function MyComponent() { const trpc = useMyApi();
const { data, error } = useQuery(trpc.users.list.queryOptions());
if (error) { return ( <div> <h2>Error occurred:</h2> <p>{error.message}</p> {error.data?.code && <p>Code: {error.data.code}</p>} </div> ); }
return ( <ul> {data.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> );}Subscriptions (Streaming)
Phần tiêu đề “Subscriptions (Streaming)”Khi kết nối đến một backend tRPC REST API, client được tạo ra sẽ tự động được cấu hình với một splitLink định tuyến các subscription operations thông qua httpSubscriptionLink (sử dụng SSE) và các query/mutation thông thường thông qua httpLink. Điều này có nghĩa là subscriptions hoạt động ngay lập tức mà không cần cấu hình bổ sung.
Để biết thông tin về cách định nghĩa các subscription procedure trong backend của bạn, xem hướng dẫn ts#trpc-api generator.
Sử dụng useSubscription Hook
Phần tiêu đề “Sử dụng useSubscription Hook”Bạn có thể sử dụng subscriptions bằng hook useSubscription với subscriptionOptions từ options proxy:
import { useSubscription } from '@trpc/tanstack-react-query';import { useMyApi } from './hooks/useMyApi';
function StreamingComponent() { const trpc = useMyApi();
const subscription = useSubscription( trpc.myStream.subscriptionOptions( { query: 'hello' }, { enabled: true, onStarted: () => { console.log('Subscription started'); }, onData: (data) => { console.log('Received:', data.text); }, onError: (error) => { console.error('Subscription error:', error); }, }, ), );
return ( <div> <p>Status: {subscription.status}</p> {subscription.data && <p>Latest: {subscription.data.text}</p>} {subscription.error && <p>Error: {subscription.error.message}</p>} <button onClick={() => subscription.reset()}>Reset</button> </div> );}Object subscription cung cấp:
subscription.data— dữ liệu nhận được gần đây nhấtsubscription.error— lỗi nhận được gần đây nhấtsubscription.status— một trong'idle','connecting','pending', hoặc'error'subscription.reset()— reset subscription (hữu ích để phục hồi từ lỗi)
Sử dụng Vanilla Client
Phần tiêu đề “Sử dụng Vanilla Client”Ngoài ra, bạn có thể sử dụng vanilla tRPC client thông qua hook use<ApiName>Client để kiểm soát tốt hơn vòng đời của subscription:
import { useState, useEffect } from 'react';import { useMyApiClient } from './hooks/useMyApi';
function StreamingComponent() { const client = useMyApiClient(); const [messages, setMessages] = useState<string[]>([]);
useEffect(() => { const subscription = client.myStream.subscribe( { query: 'hello' }, { onData: (data) => { setMessages((prev) => [...prev, data.text]); }, onComplete: () => { console.log('Stream complete'); }, onError: (error) => { console.error('Stream error:', error); }, }, );
// Dọn dẹp subscription khi unmount return () => subscription.unsubscribe(); }, [client]);
return ( <ul> {messages.map((msg, i) => ( <li key={i}>{msg}</li> ))} </ul> );}Best Practices
Phần tiêu đề “Best Practices”Xử lý trạng thái Loading
Phần tiêu đề “Xử lý trạng thái Loading”Luôn xử lý các trạng thái loading và error để có trải nghiệm người dùng tốt hơn:
function UserList() { const trpc = useMyApi();
const users = useQuery(trpc.users.list.queryOptions());
if (users.isLoading) { return <LoadingSpinner />; }
if (users.error) { return <ErrorMessage error={users.error} />; }
return ( <ul> {users.data.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> );}Optimistic Updates
Phần tiêu đề “Optimistic Updates”Sử dụng optimistic updates để có trải nghiệm người dùng tốt hơn:
import { useQueryClient, useQuery, useMutation } from '@tanstack/react-query';
function UserList() { const trpc = useMyApi(); const users = useQuery(trpc.users.list.queryOptions()); const queryClient = useQueryClient();
const deleteMutation = useMutation( trpc.users.delete.mutationOptions({ onMutate: async (userId) => { // Hủy các fetches đang gửi đi await queryClient.cancelQueries(trpc.users.list.queryFilter());
// Lấy snapshot của dữ liệu hiện tại const previousUsers = queryClient.getQueryData( trpc.users.list.queryKey(), );
// Xóa user một cách optimistic queryClient.setQueryData(trpc.users.list.queryKey(), (old) => old?.filter((user) => user.id !== userId), );
return { previousUsers }; }, onError: (err, userId, context) => { // Khôi phục dữ liệu trước đó khi có lỗi queryClient.setQueryData( trpc.users.list.queryKey(), context?.previousUsers, ); }, }), );
return ( <ul> {users.map((user) => ( <li key={user.id}> {user.name} <button onClick={() => deleteMutation.mutate(user.id)}>Delete</button> </li> ))} </ul> );}Prefetching Data
Phần tiêu đề “Prefetching Data”Prefetch dữ liệu để có hiệu suất tốt hơn:
function UserList() { const trpc = useMyApi(); const users = useQuery(trpc.users.list.queryOptions()); const queryClient = useQueryClient();
// Prefetch chi tiết user khi hover const prefetchUser = async (userId: string) => { await queryClient.prefetchQuery(trpc.users.getById.queryOptions(userId)); };
return ( <ul> {users.map((user) => ( <li key={user.id} onMouseEnter={() => prefetchUser(user.id)}> <Link to={`/users/${user.id}`}>{user.name}</Link> </li> ))} </ul> );}Infinite Queries
Phần tiêu đề “Infinite Queries”Xử lý phân trang với infinite queries:
function UserList() { const trpc = useMyApi();
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery( trpc.users.list.infiniteQueryOptions( { limit: 10 }, { getNextPageParam: (lastPage) => lastPage.nextCursor, }, ), );
return ( <div> {data?.pages.map((page) => page.users.map((user) => <UserCard key={user.id} user={user} />), )}
{hasNextPage && ( <button onClick={() => fetchNextPage()} disabled={isFetchingNextPage}> {isFetchingNextPage ? 'Loading...' : 'Load More'} </button> )} </div> );}Điều quan trọng cần lưu ý là infinite queries chỉ có thể được sử dụng cho các procedure có thuộc tính input có tên là cursor.
Type Safety
Phần tiêu đề “Type Safety”Tích hợp này cung cấp type safety hoàn toàn từ đầu đến cuối. IDE của bạn sẽ cung cấp autocompletion đầy đủ và type checking cho tất cả các API call của bạn:
function UserForm() { const trpc = useMyApi();
// ✅ Input được typed đầy đủ const createUser = trpc.users.create.useMutation();
const handleSubmit = (data: CreateUserInput) => { // ✅ Type error nếu input không khớp với schema createUser.mutate(data); };
return <form onSubmit={handleSubmit}>{/* ... */}</form>;}Các type được tự động suy ra từ các định nghĩa router và schema của backend, đảm bảo rằng bất kỳ thay đổi nào đối với API của bạn đều được phản ánh ngay lập tức trong code frontend mà không cần build.
Thông tin thêm
Phần tiêu đề “Thông tin thêm”Để biết thêm thông tin, vui lòng tham khảo: