React para tRPC
Nx Plugin for AWS fornece um gerador para integrar rapidamente sua API tRPC com um site React. Ele configura toda a configuração necessária para conectar-se aos seus backends tRPC, incluindo suporte para autenticação AWS IAM e Cognito e tratamento adequado de erros. A integração fornece segurança de tipo 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 de que sua aplicação React tenha:
- Um arquivo
main.tsxque renderiza sua aplicação - Um elemento JSX
<App/>onde o provedor tRPC será automaticamente injetado - Uma API tRPC funcionando (gerada usando o gerador de API tRPC)
- Cognito Auth adicionado via o gerador
ts#website#authse estiver conectando uma API que usa autenticação Cognito ou IAM
Exemplo da estrutura main.tsx necessária
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”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- 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
| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
| sourceProject Obrigatório | string | - | O projeto de origem |
| targetProject Obrigatório | string | - | O projeto de destino para conectar |
| sourceComponent | string | - | O componente de origem para conectar (nome do componente, caminho relativo à raiz do projeto de origem, ou id do gerador). Use '.' para selecionar explicitamente o projeto como origem. |
| targetComponent | string | - | O componente de destino para conectar (nome do componente, caminho relativo à raiz do projeto de destino, ou id do gerador). Use '.' para selecionar explicitamente o projeto como destino. |
| preferInstallDependencies | boolean | true | Se deve preferir instalar dependências após a execução do gerador. Defina como false para adiar a instalação ao executar múltiplos geradores em lote (uma instalação ainda é executada se necessário para que os geradores subsequentes possam calcular o grafo de projetos Nx); instale uma vez no final. |
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 clientes tRPC e vinculações aos seus esquema(s) de backend. ApiName será resolvido para o nome da API
- QueryClientProvider.tsx Provedor de cliente TanStack React Query
Directoryhooks
- useSigV4.tsx Hook para assinar requisições HTTP com SigV4 (somente IAM)
- use<ApiName>.tsx Um hook retornando o proxy de opções tRPC para integração com TanStack Query
- use<ApiName>Client.tsx Um hook retornando o cliente tRPC vanilla para chamadas diretas à API
Além disso, ele instala as dependências necessárias:
@trpc/client@trpc/tanstack-react-query@tanstack/react-queryaws4fetch(se estiver usando autenticação IAM)event-source-polyfill(se estiver usando REST API, para suporte a assinaturas)
Usando o Código Gerado
Seção intitulada “Usando o Código Gerado”Usando o Hook de Proxy de Opções tRPC
Seção intitulada “Usando o Hook de Proxy de Opções tRPC”O gerador fornece um hook use<ApiName> que retorna um proxy de opções tRPC para uso com hooks TanStack Query como useQuery e useMutation:
import { useQuery, useMutation } from '@tanstack/react-query';import { useMyApi } from './hooks/useMyApi';
function MyComponent() { const trpc = useMyApi();
// Example query const { data, isLoading, error } = useQuery(trpc.users.list.queryOptions());
// Example 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 Cliente tRPC Vanilla
Seção intitulada “Usando o Cliente tRPC Vanilla”O hook use<ApiName>Client fornece acesso ao cliente tRPC vanilla, que é útil para chamadas imperativas à API e assinaturas:
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 integrado que processa adequadamente os erros 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> );}Assinaturas (Streaming)
Seção intitulada “Assinaturas (Streaming)”Ao conectar-se a um backend tRPC REST API, o cliente gerado é automaticamente configurado com um splitLink que roteia operações de assinatura através de httpSubscriptionLink (usando SSE) e consultas/mutações regulares através de httpLink. Isso significa que as assinaturas funcionam imediatamente sem configuração adicional.
Para informações sobre como definir procedimentos de assinatura em seu backend, consulte o guia do gerador de API tRPC.
Usando o Hook useSubscription
Seção intitulada “Usando o Hook useSubscription”Você pode consumir assinaturas 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()— redefine a assinatura (útil para recuperar de erros)
Usando o Cliente Vanilla
Seção intitulada “Usando o Cliente Vanilla”Alternativamente, você pode usar o cliente tRPC vanilla através do hook use<ApiName>Client para mais controle sobre o ciclo de vida da assinatura:
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); }, }, );
// Clean up the subscription on unmount 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 lide com estados de carregamento e erro para uma 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 uma 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) => { // Cancel outgoing fetches await queryClient.cancelQueries(trpc.users.list.queryFilter());
// Get snapshot of current data const previousUsers = queryClient.getQueryData( trpc.users.list.queryKey(), );
// Optimistically remove the user queryClient.setQueryData(trpc.users.list.queryKey(), (old) => old?.filter((user) => user.id !== userId), );
return { previousUsers }; }, onError: (err, userId, context) => { // Restore previous data on error 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> );}Pré-buscar Dados
Seção intitulada “Pré-buscar Dados”Pré-busque dados para melhor desempenho:
function UserList() { const trpc = useMyApi(); const users = useQuery(trpc.users.list.queryOptions()); const queryClient = useQueryClient();
// Prefetch user details on 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> );}Consultas Infinitas
Seção intitulada “Consultas Infinitas”Lide com 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 ? 'Loading...' : 'Load More'} </button> )} </div> );}É importante notar que consultas infinitas só podem ser usadas para procedimentos com uma propriedade de entrada chamada cursor.
Segurança de Tipo
Seção intitulada “Segurança de Tipo”A integração fornece segurança de tipo completa de ponta a ponta. Seu IDE fornecerá autocompletar completo e verificação de tipo para todas as suas chamadas de API:
function UserForm() { const trpc = useMyApi();
// ✅ Input is fully typed const createUser = trpc.users.create.useMutation();
const handleSubmit = (data: CreateUserInput) => { // ✅ Type error if input doesn't match schema createUser.mutate(data); };
return <form onSubmit={handleSubmit}>{/* ... */}</form>;}Os tipos são automaticamente inferidos das definições de roteador e esquema do seu backend, garantindo que quaisquer alterações em sua API sejam imediatamente refletidas em seu código frontend sem a necessidade de compilar.
Autenticação Personalizada
Seção intitulada “Autenticação Personalizada”Se sua API tRPC usa autenticação Custom (Lambda Authorizer), o provedor de cliente gerado inclui headers de espaço reservado onde você deve adicionar os cabeçalhos de autorização que seu autorizador espera. Procure pelos comentários // TODO: Add headers required by your custom authorizer no <ApiName>ClientProvider.tsx gerado e substitua-os pela lógica do seu token ou chave de API.
Mais Informações
Seção intitulada “Mais Informações”Para mais informações, consulte: