React con tRPC
Il plugin Nx per AWS fornisce un generatore per integrare rapidamente la tua API tRPC con un sito React. Configura automaticamente tutto il necessario per connettersi ai backend tRPC, incluso il supporto per autenticazione AWS IAM e Cognito, oltre a una corretta gestione degli errori. L’integrazione garantisce una completa type safety end-to-end tra frontend e backend tRPC.
Prerequisiti
Sezione intitolata “Prerequisiti”Prima di utilizzare questo generatore, assicurati che la tua applicazione React abbia:
- Un file
main.tsxche renderizza l’applicazione - Un elemento JSX
<App/>dove il provider tRPC verrà iniettato automaticamente - Un’API tRPC funzionante (generata usando il generatore di API tRPC)
- Autenticazione Cognito aggiunta tramite il generatore
ts#react-website-authse si connette un’API che utilizza autenticazione Cognito o IAM
Esempio della struttura richiesta per 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>,);Utilizzo
Sezione intitolata “Utilizzo”Esegui il generatore
Sezione intitolata “Esegui il generatore”- Installa il Nx Console VSCode Plugin se non l'hai già fatto
- Apri la console Nx in VSCode
- Clicca su
Generate (UI)nella sezione "Common Nx Commands" - Cerca
@aws/nx-plugin - connection - Compila i parametri richiesti
- Clicca su
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:connectionPuoi anche eseguire una prova per vedere quali file verrebbero modificati
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-runOpzioni
Sezione intitolata “Opzioni”| Parametro | Tipo | Predefinito | Descrizione |
|---|---|---|---|
| sourceProject Obbligatorio | string | - | The source project |
| targetProject Obbligatorio | string | - | The target project to connect to |
Output del generatore
Sezione intitolata “Output del generatore”Il generatore crea la seguente struttura nella tua applicazione React:
Directorysrc
Directorycomponents
- <ApiName>ClientProvider.tsx Configura i client tRPC e il binding agli schemi del backend. ApiName corrisponderà al nome dell’API
- QueryClientProvider.tsx Provider del client TanStack React Query
Directoryhooks
- useSigV4.tsx Hook per firmare richieste HTTP con SigV4 (solo IAM)
- use<ApiName>.tsx Hook che restituisce il proxy delle opzioni tRPC per l’integrazione con TanStack Query
- use<ApiName>Client.tsx Hook che restituisce il client vanilla tRPC per chiamate API dirette
Inoltre, installa le dipendenze necessarie:
@trpc/client@trpc/tanstack-react-query@tanstack/react-queryaws4fetch(se si utilizza autenticazione IAM)event-source-polyfill(se si utilizza REST API, per il supporto alle subscription)
Utilizzo del codice generato
Sezione intitolata “Utilizzo del codice generato”Utilizzo dell’hook proxy delle opzioni tRPC
Sezione intitolata “Utilizzo dell’hook proxy delle opzioni tRPC”Il generatore fornisce un hook use<ApiName> che restituisce un proxy delle opzioni tRPC da utilizzare con gli hook di TanStack Query come useQuery e useMutation:
import { useQuery, useMutation } from '@tanstack/react-query';import { useMyApi } from './hooks/useMyApi';
function MyComponent() { const trpc = useMyApi();
// Esempio di query const { data, isLoading, error } = useQuery(trpc.users.list.queryOptions());
// Esempio di mutation const mutation = useMutation(trpc.users.create.mutationOptions());
const handleCreate = () => { mutation.mutate({ name: 'John Doe', email: 'john@example.com', }); };
if (isLoading) return <div>Caricamento...</div>;
return ( <ul> {data.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> );}Utilizzo del client vanilla tRPC
Sezione intitolata “Utilizzo del client vanilla tRPC”L’hook use<ApiName>Client fornisce accesso al client vanilla tRPC, utile per chiamate API imperative e subscription:
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>;}Gestione degli errori
Sezione intitolata “Gestione degli errori”L’integrazione include una gestione degli errori che processa correttamente gli errori tRPC:
function MyComponent() { const trpc = useMyApi();
const { data, error } = useQuery(trpc.users.list.queryOptions());
if (error) { return ( <div> <h2>Errore:</h2> <p>{error.message}</p> {error.data?.code && <p>Codice: {error.data.code}</p>} </div> ); }
return ( <ul> {data.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> );}Subscription (Streaming)
Sezione intitolata “Subscription (Streaming)”Quando ci si connette a un backend tRPC REST API, il client generato viene automaticamente configurato con un splitLink che instrada le operazioni di subscription attraverso httpSubscriptionLink (utilizzando SSE) e le query/mutation regolari attraverso httpLink. Ciò significa che le subscription funzionano immediatamente senza configurazione aggiuntiva.
Per informazioni su come definire procedure di subscription nel backend, consulta la guida al generatore ts#trpc-api.
Utilizzo dell’hook useSubscription
Sezione intitolata “Utilizzo dell’hook useSubscription”Puoi consumare subscription utilizzando l’hook useSubscription con subscriptionOptions dal proxy delle opzioni:
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> );}L’oggetto subscription fornisce:
subscription.data— i dati ricevuti più recentementesubscription.error— l’errore ricevuto più recentementesubscription.status— uno tra'idle','connecting','pending', o'error'subscription.reset()— resetta la subscription (utile per recuperare dagli errori)
Utilizzo del client vanilla
Sezione intitolata “Utilizzo del client vanilla”In alternativa, puoi utilizzare il client vanilla tRPC tramite l’hook use<ApiName>Client per avere maggiore controllo sul ciclo di vita della 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); }, }, );
// Pulisce la subscription allo smontaggio return () => subscription.unsubscribe(); }, [client]);
return ( <ul> {messages.map((msg, i) => ( <li key={i}>{msg}</li> ))} </ul> );}Best Practices
Sezione intitolata “Best Practices”Gestione degli stati di caricamento
Sezione intitolata “Gestione degli stati di caricamento”Gestisci sempre gli stati di caricamento e errore per una migliore user experience:
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> );}Aggiornamenti ottimistici
Sezione intitolata “Aggiornamenti ottimistici”Utilizza aggiornamenti ottimistici per una user experience migliore:
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) => { // Interrompi le richieste in corso await queryClient.cancelQueries(trpc.users.list.queryFilter());
// Ottieni snapshot dei dati correnti const previousUsers = queryClient.getQueryData( trpc.users.list.queryKey(), );
// Rimuovi utente in modo ottimistico queryClient.setQueryData(trpc.users.list.queryKey(), (old) => old?.filter((user) => user.id !== userId), );
return { previousUsers }; }, onError: (err, userId, context) => { // Ripristina dati precedenti in caso di errore 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)}>Elimina</button> </li> ))} </ul> );}Prefetch dei dati
Sezione intitolata “Prefetch dei dati”Esegui prefetch dei dati per migliorare le performance:
function UserList() { const trpc = useMyApi(); const users = useQuery(trpc.users.list.queryOptions()); const queryClient = useQueryClient();
// Prefetch dei dettagli utente al passaggio del 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> );}Query infinite
Sezione intitolata “Query infinite”Gestisci la paginazione con query infinite:
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 ? 'Caricamento...' : 'Carica altro'} </button> )} </div> );}È importante notare che le query infinite possono essere utilizzate solo per procedure con una proprietà di input chiamata cursor.
Type Safety
Sezione intitolata “Type Safety”L’integrazione garantisce una completa type safety end-to-end. La tua IDE fornirà autocompletamento e type checking per tutte le chiamate API:
function UserForm() { const trpc = useMyApi();
// ✅ L'input è completamente tipizzato const createUser = trpc.users.create.useMutation();
const handleSubmit = (data: CreateUserInput) => { // ✅ Errore di tipo se l'input non corrisponde allo schema createUser.mutate(data); };
return <form onSubmit={handleSubmit}>{/* ... */}</form>;}I tipi sono automaticamente derivati dal router e dagli schemi del backend, garantendo che qualsiasi modifica all’API si rifletta immediatamente nel codice frontend senza necessità di build.
Ulteriori informazioni
Sezione intitolata “Ulteriori informazioni”Per maggiori informazioni, consulta: