Reagir ao tRPC
O Nx Plugin para AWS fornece um gerador para integrar rapidamente sua API tRPC com um site React. Ele configura toda a configuração necessária para conectar aos seus backends tRPC, incluindo suporte a autenticação AWS IAM e Cognito 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
Seção intitulada “Pré-requisitos”Antes de usar este gerador, certifique-se que sua aplicação React possui:
- Um arquivo
main.tsxque renderiza sua aplicação - Um elemento JSX
<App/>onde o provider tRPC será injetado automaticamente - Uma API tRPC funcional (gerada usando o gerador de API tRPC)
- Cognito Auth adicionado via gerador
ts#react-website-authse conectando a uma API que usa autenticação Cognito ou IAM
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>,);Executar o Gerador
Seção intitulada “Executar o Gerador”- Instale o Nx Console VSCode Plugin se ainda não o fez
- Abra o console Nx no VSCode
- Clique em
Generate (UI)na seção "Common Nx Commands" - Procure por
@aws/nx-plugin - connection - Preencha os parâmetros obrigatórios
- Clique em
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:connectionVocê também pode realizar uma execução simulada para ver quais arquivos seriam alterados
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-run| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
| sourceProject Obrigatório | string | - | The source project |
| targetProject Obrigatório | string | - | The target project to connect to |
Saída do Gerador
Seção intitulada “Saída do Gerador”O gerador cria a seguinte estrutura em sua aplicação React:
Directorysrc
Directorycomponents
- <ApiName>ClientProvider.tsx Configura os clients tRPC e vinculações aos schemas do backend. ApiName será resolvido para o nome da API
- QueryClientProvider.tsx Provider do client TanStack React Query
Directoryhooks
- useSigV4.tsx Hook para assinar requisições HTTP com SigV4 (apenas IAM)
- use<ApiName>.tsx Um hook que retorna o proxy de opções tRPC para integração com TanStack Query
- use<ApiName>Client.tsx Um hook que retorna o client tRPC vanilla para chamadas diretas à API
Além disso, instala as dependências necessárias:
@trpc/client@trpc/tanstack-react-query@tanstack/react-queryaws4fetch(se usando autenticação IAM)event-source-polyfill(se usando REST API, para suporte a subscriptions)
Usando o Código Gerado
Seção intitulada “Usando o Código Gerado”Usando o Hook Proxy de Opções tRPC
Seção intitulada “Usando o Hook Proxy de Opções tRPC”O gerador fornece um hook use<ApiName> que retorna um proxy de opções tRPC para uso com hooks do TanStack Query como useQuery e useMutation:
import { useQuery, useMutation } from '@tanstack/react-query';import { useMyApi } from './hooks/useMyApi';
function MyComponent() { const trpc = useMyApi();
// Exemplo de query const { data, isLoading, error } = useQuery(trpc.users.list.queryOptions());
// Exemplo de 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> );}Usando o Client tRPC Vanilla
Seção intitulada “Usando o Client tRPC Vanilla”O hook use<ApiName>Client fornece acesso ao client tRPC vanilla, que é útil para chamadas imperativas à API e 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>;}Tratamento de Erros
Seção intitulada “Tratamento de Erros”A integração inclui tratamento de erros interno 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> );}Subscriptions (Streaming)
Seção intitulada “Subscriptions (Streaming)”Ao conectar a um backend tRPC REST API, o client gerado é automaticamente configurado com um splitLink que roteia operações de subscription através de httpSubscriptionLink (usando SSE) e queries/mutations regulares através de httpLink. Isso significa que subscriptions funcionam imediatamente sem nenhuma configuração adicional.
Para informações sobre como definir procedimentos de subscription em seu backend, consulte o guia do gerador ts#trpc-api.
Usando o Hook useSubscription
Seção intitulada “Usando o Hook useSubscription”Você pode consumir subscriptions usando o hook useSubscription com subscriptionOptions do proxy de opções:
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> );}O objeto subscription fornece:
subscription.data— os dados recebidos mais recentementesubscription.error— o erro recebido mais recentementesubscription.status— um de'idle','connecting','pending', ou'error'subscription.reset()— reseta a subscription (útil para recuperar de erros)
Usando o Client Vanilla
Seção intitulada “Usando o Client Vanilla”Alternativamente, você pode usar o client tRPC vanilla através do hook use<ApiName>Client para mais controle sobre o ciclo de vida da 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); }, }, );
// Limpa a subscription ao desmontar return () => subscription.unsubscribe(); }, [client]);
return ( <ul> {messages.map((msg, i) => ( <li key={i}>{msg}</li> ))} </ul> );}Melhores Práticas
Seção intitulada “Melhores Práticas”Lidar com Estados de Carregamento
Seção intitulada “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
Seção intitulada “Atualizações Otimistas”Use atualizações otimistas para melhorar a 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 otimisticamente 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
Seção intitulada “Pré-busca de Dados”Faça pré-busca de 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
Seção intitulada “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
Seção intitulada “Segurança de Tipos”A integração fornece segurança de tipos completa de ponta a ponta. Seu IDE oferecerá autocompletar completo e verificação de tipos para todas as 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 do router e definições de schema do backend, garantindo que quaisquer mudanças na API sejam imediatamente refletidas no código frontend sem necessidade de build.
Mais Informações
Seção intitulada “Mais Informações”Para mais informações, consulte: