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

Exécuter ce générateur@aws/nx-plugin:connection

pnpm nx g @aws/nx-plugin:connection
Composez votre commande5

Requis

Requis

Options du générateur5 options
sourceProjectRequisstring

Le projet source

targetProjectRequisstring

Le projet cible auquel se connecter

sourceComponentstring

Le composant source depuis lequel se connecter (nom du composant, chemin relatif à la racine du projet source, ou identifiant de générateur). Utilisez '.' pour sélectionner explicitement le projet comme source.

targetComponentstring

Le composant cible auquel se connecter (nom du composant, chemin relatif à la racine du projet cible, ou identifiant de générateur). Utilisez '.' pour sélectionner explicitement le projet comme cible.

preferInstallDependenciesbooleanPar défaut: true

Indique 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 Exporte à la fois use<ApiName>, renvoyant le proxy d’options tRPC pour l’intégration TanStack Query, et use<ApiName>Client, 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>;
if (error) return <div>Error: {error.message}</div>;
if (!data) return null;
return (
<ul>
{data.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}

data est undefined jusqu’à ce que la requête soit résolue, donc protégez-la avant de la déréférencer.

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>
);
}
if (!data) return null;
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.isPending) {
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.data?.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.data?.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 :

import { useMutation } from '@tanstack/react-query';
function UserForm() {
const trpc = useMyApi();
// ✅ Input is fully typed
const createUser = useMutation(trpc.users.create.mutationOptions());
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 à :