Pular para o conteúdo

Reagir ao tRPC

O AWS Plugin para Nx fornece um gerador para integrar rapidamente sua API tRPC com um site React. Ele configura toda a infraestrutura necessária para conexão com seus backends tRPC, incluindo suporte a autenticação IAM da AWS e tratamento adequado de erros. A integração oferece segurança de tipos completa de ponta a ponta entre seu frontend e backend(s) tRPC.

Pré-requisitos

Antes de usar este gerador, certifique-se que sua aplicação React possui:

  1. Um arquivo main.tsx que renderiza sua aplicação
  2. Um elemento JSX <App/> onde o provider tRPC será injetado automaticamente
  3. Um backend tRPC funcional (gerado usando o gerador de backend tRPC)
Exemplo da estrutura necessária do main.tsx
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>,
);

Utilização

Executar o Gerador

  1. Instale o Nx Console VSCode Plugin se ainda não o fez
  2. Abra o console Nx no VSCode
  3. Clique em Generate (UI) na seção "Common Nx Commands"
  4. Procure por @aws/nx-plugin - api-connection
  5. Preencha os parâmetros obrigatórios
    • Clique em Generate

    Opções

    Parâmetro Tipo Padrão Descrição
    sourceProject Obrigatório string - The source project which will call the API
    targetProject Obrigatório string - The target project containing your API
    auth string IAM Authentication strategy (choose from IAM or None)

    Saída do Gerador

    O gerador cria a seguinte estrutura em sua aplicação React:

    • Directorysrc
      • Directorycomponents
        • DirectoryTrpcClients
          • index.tsx
          • TrpcProvider.tsx Provider reutilizável para múltiplas APIs tRPC
          • TrpcApis.tsx Objeto contendo todas as conexões de API tRPC
          • TrpcClientProviders.tsx Configura os clientes tRPC e vinculações aos schemas do backend
        • QueryClientProvider.tsx Provider do cliente TanStack React Query
      • Directoryhooks
        • useSigV4.tsx Hook para assinar requisições HTTP com SigV4 (apenas IAM)
        • use<ApiName>.tsx Hook específico para a API do backend. ApiName será resolvido para o nome da API

    Adicionalmente, instala as dependências necessárias:

    • @trpc/client
    • @trpc/tanstack-react-query
    • @tanstack/react-query
    • aws4fetch (se usando autenticação IAM)

    Usando o Código Gerado

    Usando o Hook tRPC

    O gerador fornece um hook use<ApiName> que dá acesso ao cliente tRPC com segurança de tipos:

    import { useQuery, useMutation } from '@tanstack/react-query';
    import { useMyApi } from './hooks/useMyApi';
    function MyComponent() {
    const trpc = useMyApi();
    // Exemplo de consulta
    const { data, isLoading, error } = useQuery(trpc.users.list.queryOptions());
    // Exemplo de mutação
    const mutation = useMutation(trpc.users.create.mutationOptions());
    const handleCreate = () => {
    mutation.mutate({
    name: 'John Doe',
    email: 'john@example.com',
    });
    };
    if (isLoading) return <div>Carregando...</div>;
    return (
    <ul>
    {data.map((user) => (
    <li key={user.id}>{user.name}</li>
    ))}
    </ul>
    );
    }

    Tratamento de Erros

    A integração inclui tratamento de erros integrado que processa adequadamente erros tRPC:

    function MyComponent() {
    const trpc = useMyApi();
    const { data, error } = useQuery(trpc.users.list.queryOptions());
    if (error) {
    return (
    <div>
    <h2>Ocorreu um erro:</h2>
    <p>{error.message}</p>
    {error.data?.code && <p>Código: {error.data.code}</p>}
    </div>
    );
    }
    return (
    <ul>
    {data.map((user) => (
    <li key={user.id}>{user.name}</li>
    ))}
    </ul>
    );
    }

    Melhores Práticas

    Lidar com Estados de Carregamento

    Sempre trate estados de carregamento e erro para melhor experiência do usuário:

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

    Atualizações Otimistas

    Use atualizações otimistas para melhor experiência do usuário:

    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) => {
    // Cancela requisições pendentes
    await queryClient.cancelQueries(trpc.users.list.queryFilter());
    // Obtém snapshot dos dados atuais
    const previousUsers = queryClient.getQueryData(
    trpc.users.list.queryKey(),
    );
    // Remove otimistamente o usuário
    queryClient.setQueryData(trpc.users.list.queryKey(), (old) =>
    old?.filter((user) => user.id !== userId),
    );
    return { previousUsers };
    },
    onError: (err, userId, context) => {
    // Restaura dados anteriores em caso de erro
    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)}>Excluir</button>
    </li>
    ))}
    </ul>
    );
    }

    Pré-busca de Dados

    Pré-busque dados para melhor performance:

    function UserList() {
    const trpc = useMyApi();
    const users = useQuery(trpc.users.list.queryOptions());
    const queryClient = useQueryClient();
    // Pré-busca detalhes do usuário ao passar o mouse
    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>
    );
    }

    Consultas Infinitas

    Gerencie paginação com consultas infinitas:

    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 ? 'Carregando...' : 'Carregar Mais'}
    </button>
    )}
    </div>
    );
    }

    É importante notar que consultas infinitas só podem ser usadas para procedimentos com uma propriedade de input chamada cursor.

    Segurança de Tipos

    A integração fornece segurança de tipos completa de ponta a ponta. Sua IDE oferecerá autocompletar e verificação de tipos para todas chamadas de API:

    function UserForm() {
    const trpc = useMyApi();
    // ✅ Input totalmente tipado
    const createUser = trpc.users.create.useMutation();
    const handleSubmit = (data: CreateUserInput) => {
    // ✅ Erro de tipo se input não corresponder ao schema
    createUser.mutate(data);
    };
    return <form onSubmit={handleSubmit}>{/* ... */}</form>;
    }

    Os tipos são inferidos automaticamente das definições do router e schema do seu backend, garantindo que quaisquer mudanças na API sejam imediatamente refletidas no código frontend sem necessidade de build.

    Mais Informações

    Para mais informações, consulte a documentação tRPC TanStack React Query.

    Você também pode consultar diretamente a documentação do TanStack Query.