Reaccionar a tRPC
Nx Plugin for AWS proporciona un generador para integrar rápidamente tu API tRPC con un sitio web en React. Configura toda la infraestructura necesaria para conectar con tus backends tRPC, incluyendo soporte para autenticación con AWS IAM y Cognito, además de manejo adecuado de errores. La integración provee seguridad de tipos de extremo a extremo entre tu frontend y backend(s) tRPC.
Prerrequisitos
Sección titulada «Prerrequisitos»Antes de usar este generador, asegúrate que tu aplicación React tenga:
- Un archivo
main.tsxque renderice tu aplicación - Un elemento JSX
<App/>donde se inyectará automáticamente el proveedor tRPC - Una API tRPC funcional (generada usando el generador de API tRPC)
- Cognito Auth agregado mediante el generador
ts#react-website-authsi conectas una API que use autenticación Cognito o IAM
Ejemplo de estructura requerida en 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>,);Ejecutar el generador
Sección titulada «Ejecutar el generador»- Instale el Nx Console VSCode Plugin si aún no lo ha hecho
- Abra la consola Nx en VSCode
- Haga clic en
Generate (UI)en la sección "Common Nx Commands" - Busque
@aws/nx-plugin - connection - Complete los parámetros requeridos
- Haga clic en
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:connectionTambién puede realizar una ejecución en seco para ver qué archivos se cambiarían
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-runOpciones
Sección titulada «Opciones»| Parámetro | Tipo | Predeterminado | Descripción |
|---|---|---|---|
| sourceProject Requerido | string | - | The source project |
| targetProject Requerido | string | - | The target project to connect to |
Salida del generador
Sección titulada «Salida del generador»El generador crea la siguiente estructura en tu aplicación React:
Directoriosrc
Directoriocomponents
- <ApiName>ClientProvider.tsx Configura los clientes tRPC y vinculaciones con tus esquemas de backend. ApiName se resolverá al nombre de la API
- QueryClientProvider.tsx Proveedor del cliente TanStack React Query
Directoriohooks
- useSigV4.tsx Hook para firmar peticiones HTTP con SigV4 (solo IAM)
- use<ApiName>.tsx Hook que retorna el proxy de opciones tRPC para integración con TanStack Query
- use<ApiName>Client.tsx Hook que retorna el cliente tRPC vanilla para llamadas directas a la API
Adicionalmente, instala las dependencias requeridas:
@trpc/client@trpc/tanstack-react-query@tanstack/react-queryaws4fetch(si se usa autenticación IAM)event-source-polyfill(si se usa REST API, para soporte de suscripciones)
Usando el código generado
Sección titulada «Usando el código generado»Usando el Hook Proxy de Opciones tRPC
Sección titulada «Usando el Hook Proxy de Opciones tRPC»El generador provee un hook use<ApiName> que retorna un proxy de opciones tRPC para usar con hooks de TanStack Query como useQuery y useMutation:
import { useQuery, useMutation } from '@tanstack/react-query';import { useMyApi } from './hooks/useMyApi';
function MyComponent() { const trpc = useMyApi();
// Ejemplo de consulta const { data, isLoading, error } = useQuery(trpc.users.list.queryOptions());
// Ejemplo de mutación const mutation = useMutation(trpc.users.create.mutationOptions());
const handleCreate = () => { mutation.mutate({ name: 'John Doe', email: 'john@example.com', }); };
if (isLoading) return <div>Cargando...</div>;
return ( <ul> {data.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> );}Usando el Cliente tRPC Vanilla
Sección titulada «Usando el Cliente tRPC Vanilla»El hook use<ApiName>Client proporciona acceso al cliente tRPC vanilla, que es útil para llamadas imperativas a la API y suscripciones:
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>;}Manejo de errores
Sección titulada «Manejo de errores»La integración incluye manejo de errores integrado que procesa correctamente los errores tRPC:
function MyComponent() { const trpc = useMyApi();
const { data, error } = useQuery(trpc.users.list.queryOptions());
if (error) { return ( <div> <h2>Ocurrió un error:</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> );}Suscripciones (Streaming)
Sección titulada «Suscripciones (Streaming)»Al conectar con un backend tRPC de REST API, el cliente generado se configura automáticamente con un splitLink que enruta las operaciones de suscripción a través de httpSubscriptionLink (usando SSE) y las consultas/mutaciones regulares a través de httpLink. Esto significa que las suscripciones funcionan de inmediato sin configuración adicional.
Para información sobre cómo definir procedimientos de suscripción en tu backend, consulta la guía del generador ts#trpc-api.
Usando el Hook useSubscription
Sección titulada «Usando el Hook useSubscription»Puedes consumir suscripciones usando el hook useSubscription con subscriptionOptions del proxy de opciones:
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> );}El objeto subscription proporciona:
subscription.data— los datos recibidos más recientementesubscription.error— el error recibido más recientementesubscription.status— uno de'idle','connecting','pending', o'error'subscription.reset()— reinicia la suscripción (útil para recuperarse de errores)
Usando el Cliente Vanilla
Sección titulada «Usando el Cliente Vanilla»Alternativamente, puedes usar el cliente tRPC vanilla a través del hook use<ApiName>Client para más control sobre el ciclo de vida de la suscripción:
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); }, }, );
// Limpiar la suscripción al desmontar return () => subscription.unsubscribe(); }, [client]);
return ( <ul> {messages.map((msg, i) => ( <li key={i}>{msg}</li> ))} </ul> );}Mejores prácticas
Sección titulada «Mejores prácticas»Manejar estados de carga
Sección titulada «Manejar estados de carga»Siempre maneja los estados de carga y error para una mejor experiencia de usuario:
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> );}Actualizaciones optimistas
Sección titulada «Actualizaciones optimistas»Usa actualizaciones optimistas para una mejor experiencia de usuario:
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) => { // Cancelar fetches pendientes await queryClient.cancelQueries(trpc.users.list.queryFilter());
// Obtener snapshot de datos actuales const previousUsers = queryClient.getQueryData( trpc.users.list.queryKey(), );
// Eliminar usuario optimistamente queryClient.setQueryData(trpc.users.list.queryKey(), (old) => old?.filter((user) => user.id !== userId), );
return { previousUsers }; }, onError: (err, userId, context) => { // Restaurar datos anteriores en 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)}>Eliminar</button> </li> ))} </ul> );}Precarga de datos
Sección titulada «Precarga de datos»Precarga datos para mejor rendimiento:
function UserList() { const trpc = useMyApi(); const users = useQuery(trpc.users.list.queryOptions()); const queryClient = useQueryClient();
// Precargar detalles de usuario al hacer 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
Sección titulada «Consultas infinitas»Maneja paginación con 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 ? 'Cargando...' : 'Cargar más'} </button> )} </div> );}Es importante notar que las consultas infinitas solo pueden usarse para procedimientos con una propiedad de entrada llamada cursor.
Seguridad de tipos
Sección titulada «Seguridad de tipos»La integración provee seguridad de tipos completa de extremo a extremo. Tu IDE proveerá autocompletado y verificación de tipos para todas las llamadas a la API:
function UserForm() { const trpc = useMyApi();
// ✅ La entrada está totalmente tipada const createUser = trpc.users.create.useMutation();
const handleSubmit = (data: CreateUserInput) => { // ✅ Error de tipo si la entrada no coincide con el esquema createUser.mutate(data); };
return <form onSubmit={handleSubmit}>{/* ... */}</form>;}Los tipos se infieren automáticamente desde las definiciones del router y esquema de tu backend, garantizando que cualquier cambio en tu API se refleje inmediatamente en tu código frontend sin necesidad de compilar.
Más información
Sección titulada «Más información»Para más información, consulta: