Bỏ qua để đến nội dung

React đến tRPC

AWS Plugin cho Nx 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.

Trước khi sử dụng generator này, đảm bảo ứng dụng React của bạn có:

  1. Một file main.tsx render ứng dụng của bạn
  2. Một phần tử JSX <App/> nơi mà tRPC provider sẽ được tự động inject vào
  3. Một tRPC API đang hoạt động (được tạo bằng tRPC API generator)
  4. Cognito Auth được thêm thông qua ts#react-website-auth generator 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>,
);
  1. Install the Nx Console VSCode Plugin if you haven't already
  2. Open the Nx Console in VSCode
  3. Click Generate (UI) in the "Common Nx Commands" section
  4. Search for @aws/nx-plugin - api-connection
  5. Fill in the required parameters
    • Click Generate
    Parameter Type Default Description
    sourceProject Required string - The source project which will call the API
    targetProject Required string - The target project containing your API

    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 cho backend API đã cho.

    Ngoài ra, nó cài đặt các dependencies bắt buộc:

    • @trpc/client
    • @trpc/tanstack-react-query
    • @tanstack/react-query
    • aws4fetch (nếu sử dụng IAM auth)

    Generator cung cấp một hook use<ApiName> cho bạn truy cập đến tRPC client có type-safe:

    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>
    );
    }

    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>
    );
    }

    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>
    );
    }

    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>
    );
    }

    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>
    );
    }

    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.

    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.

    Để biết thêm thông tin, vui lòng tham khảo tài liệu tRPC TanStack React Query.

    Bạn cũng có thể tham khảo trực tiếp tài liệu TanStack Query.