Aller au contenu

React vers tRPC

Nx Plugin for AWS fournit un générateur pour intégrer rapidement votre API tRPC avec un site web React. Il configure tous les paramètres nécessaires pour se connecter à vos backends tRPC, y compris la prise en charge de l’authentification AWS IAM et Cognito et une gestion appropriée des erreurs. L’intégration fournit une sécurité de type de bout en bout complète entre votre frontend et votre ou vos backends tRPC.

Avant d’utiliser ce générateur, assurez-vous que votre application React dispose de :

  1. Un fichier main.tsx qui rend votre application
  2. Un élément JSX <App/> où le fournisseur tRPC sera automatiquement injecté
  3. Une API tRPC fonctionnelle (générée à l’aide du générateur d’API tRPC)
  4. Cognito Auth ajouté via le générateur ts#website#auth si vous vous connectez à une API qui utilise l’authentification Cognito ou IAM
Exemple de structure main.tsx requise
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>,
);
Terminal window
pnpm nx g @aws/nx-plugin:connection
Vous pouvez également effectuer une simulation pour voir quels fichiers seraient modifiés
Terminal window
pnpm nx g @aws/nx-plugin:connection --dry-run
ParamètreTypePar défautDescription
sourceProject Requisstring-Le projet source
targetProject Requisstring-Le projet cible auquel se connecter
sourceComponent string-Le composant source depuis lequel se connecter (nom du composant, chemin relatif à la racine du projet source, ou identifiant du générateur). Utilisez '.' pour sélectionner explicitement le projet comme source.
targetComponent string-Le composant cible auquel se connecter (nom du composant, chemin relatif à la racine du projet cible, ou identifiant du générateur). Utilisez '.' pour sélectionner explicitement le projet comme cible.
preferInstallDependencies booleantrueIndique s'il faut privilégier l'installation des dépendances après l'exécution du générateur. Définir à false pour différer l'installation lors de l'exécution de plusieurs générateurs en lot (une installation s'exécute quand même si nécessaire pour que les générateurs suivants puissent calculer le graphe de projet Nx) ; installer une seule fois à la fin.

Le générateur crée la structure suivante dans votre application React :

  • Répertoiresrc
    • Répertoirecomponents
      • <ApiName>ClientProvider.tsx Configure les clients tRPC et les liaisons avec votre ou vos schémas backend. ApiName sera résolu au nom de l’API
      • QueryClientProvider.tsx Fournisseur de client TanStack React Query
    • Répertoirehooks
      • useSigV4.tsx Hook pour signer les requêtes HTTP avec SigV4 (IAM uniquement)
      • use<ApiName>.tsx Un hook renvoyant le proxy d’options tRPC pour l’intégration TanStack Query
      • use<ApiName>Client.tsx Un hook renvoyant le client tRPC vanilla pour les appels d’API directs

De plus, il installe les dépendances requises :

  • @trpc/client
  • @trpc/tanstack-react-query
  • @tanstack/react-query
  • aws4fetch (si vous utilisez l’authentification IAM)
  • event-source-polyfill (si vous utilisez l’API REST, pour la prise en charge des abonnements)

Le générateur fournit un hook use<ApiName> qui renvoie un proxy d’options tRPC à utiliser avec les hooks TanStack Query comme useQuery et 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>
);
}

Le hook use<ApiName>Client fournit un accès au client tRPC vanilla, qui est utile pour les appels d’API impératifs et les abonnements :

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

L’intégration inclut une gestion des erreurs intégrée qui traite correctement les erreurs 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>
);
}

Lors de la connexion à un backend tRPC d’API REST, le client généré est automatiquement configuré avec un splitLink qui achemine les opérations d’abonnement via httpSubscriptionLink (en utilisant SSE) et les requêtes/mutations régulières via httpLink. Cela signifie que les abonnements fonctionnent immédiatement sans configuration supplémentaire.

Pour plus d’informations sur la façon de définir des procédures d’abonnement dans votre backend, consultez le guide du générateur d’API tRPC.

Vous pouvez consommer des abonnements en utilisant le hook useSubscription avec subscriptionOptions du proxy d’options :

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’objet subscription fournit :

  • subscription.data — les données les plus récemment reçues
  • subscription.error — l’erreur la plus récemment reçue
  • subscription.status — l’un de 'idle', 'connecting', 'pending' ou 'error'
  • subscription.reset() — réinitialise l’abonnement (utile pour récupérer après des erreurs)

Alternativement, vous pouvez utiliser le client tRPC vanilla via le hook use<ApiName>Client pour plus de contrôle sur le cycle de vie de l’abonnement :

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

Gérez toujours les états de chargement et d’erreur pour une meilleure expérience utilisateur :

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

Utilisez des mises à jour optimistes pour une meilleure expérience utilisateur :

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échargez les données pour de meilleures performances :

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

Gérez la pagination avec des requêtes infinies :

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

Il est important de noter que les requêtes infinies ne peuvent être utilisées que pour les procédures avec une propriété d’entrée nommée cursor.

L’intégration fournit une sécurité de type de bout en bout complète. Votre IDE fournira une autocomplétion complète et une vérification de type pour tous vos appels d’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>;
}

Les types sont automatiquement déduits des définitions de routeur et de schéma de votre backend, garantissant que toute modification de votre API est immédiatement reflétée dans votre code frontend sans avoir besoin de construire.

Si votre API tRPC utilise l’authentification Custom (Lambda Authorizer), le fournisseur de client généré inclut des headers d’espace réservé où vous devez ajouter les en-têtes d’autorisation attendus par votre autoriseur. Recherchez les commentaires // TODO: Add headers required by your custom authorizer dans le fichier <ApiName>ClientProvider.tsx généré et remplacez-les par votre logique de jeton ou de clé API.

Pour plus d’informations, veuillez vous référer à :