React から tRPC へ
Nx Plugin for AWS は、tRPC API を React ウェブサイトと素早く統合するためのジェネレーターを提供します。AWS IAM と Cognito 認証のサポート、適切なエラーハンドリングを含む、tRPC バックエンドへの接続に必要なすべての設定をセットアップします。この統合により、フロントエンドと tRPC バックエンド間で完全なエンドツーエンドの型安全性が提供されます。
このジェネレーターを使用する前に、React アプリケーションが以下を備えていることを確認してください:
- アプリケーションをレンダリングする
main.tsxファイル - tRPC プロバイダーが自動的に注入される
<App/>JSX 要素 - 動作する tRPC API (tRPC API ジェネレーターを使用して生成)
- Cognito または IAM 認証を使用する API に接続する場合は、
ts#website#authジェネレーターを介して追加された Cognito Auth
必要な 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>,);ジェネレーターを実行する
Section titled “ジェネレーターを実行する”pnpm nx g @aws/nx-plugin:connectionyarn nx g @aws/nx-plugin:connectionnpx nx g @aws/nx-plugin:connectionbunx nx g @aws/nx-plugin:connection- インストール Nx Console VSCode Plugin まだインストールしていない場合
- VSCodeでNxコンソールを開く
- クリック
Generate (UI)"Common Nx Commands"セクションで - 検索
@aws/nx-plugin - connection - 必須パラメータを入力
- クリック
Generate
| パラメータ | 型 | デフォルト | 説明 |
|---|---|---|---|
| sourceProject 必須 | string | - | ソース プロジェクト |
| targetProject 必須 | string | - | 接続先のターゲット プロジェクト |
| sourceComponent | string | - | 接続元のソース コンポーネント (コンポーネント名、ソース プロジェクト ルートからの相対パス、またはジェネレーター ID)。プロジェクトをソースとして明示的に選択するには '.' を使用します。 |
| targetComponent | string | - | 接続先のターゲット コンポーネント (コンポーネント名、ターゲット プロジェクト ルートからの相対パス、またはジェネレーター ID)。プロジェクトをターゲットとして明示的に選択するには '.' を使用します。 |
| preferInstallDependencies | boolean | true | ジェネレーター実行後に依存関係のインストールを優先するかどうか。複数のジェネレーターをバッチ処理する際にインストールを延期する場合はfalseに設定します(後続のジェネレーターがNxプロジェクトグラフを計算できるよう、必要に応じてインストールは実行されます)。最後に一度だけインストールします。 |
ジェネレーターの出力
Section titled “ジェネレーターの出力”ジェネレーターは、React アプリケーションに以下の構造を作成します:
Directorysrc
Directorycomponents
- <ApiName>ClientProvider.tsx Sets up the tRPC clients and bindings to your backend schema(s). ApiName will resolve to the name of the API
- QueryClientProvider.tsx TanStack React Query client provider
Directoryhooks
- useSigV4.tsx Hook for signing HTTP requests with SigV4 (IAM only)
- use<ApiName>.tsx A hook returning the tRPC options proxy for TanStack Query integration
- use<ApiName>Client.tsx A hook returning the vanilla tRPC client for direct API calls
さらに、必要な依存関係をインストールします:
@trpc/client@trpc/tanstack-react-query@tanstack/react-queryaws4fetch(IAM 認証を使用する場合)event-source-polyfill(REST API を使用する場合、サブスクリプションサポート用)
生成されたコードの使用
Section titled “生成されたコードの使用”tRPC オプションプロキシフックの使用
Section titled “tRPC オプションプロキシフックの使用”ジェネレーターは、useQuery や useMutation などの TanStack Query フックで使用するための tRPC オプションプロキシを返す use<ApiName> フックを提供します:
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> );}バニラ tRPC クライアントの使用
Section titled “バニラ tRPC クライアントの使用”use<ApiName>Client フックは、命令的な API 呼び出しやサブスクリプションに便利な バニラ tRPC クライアントへのアクセスを提供します:
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>;}エラーハンドリング
Section titled “エラーハンドリング”統合には、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> );}サブスクリプション (ストリーミング)
Section titled “サブスクリプション (ストリーミング)”REST API tRPC バックエンドに接続する場合、生成されたクライアントは自動的に splitLink で設定され、サブスクリプション操作を httpSubscriptionLink (SSE を使用) 経由でルーティングし、通常のクエリ/ミューテーションを httpLink 経由でルーティングします。つまり、サブスクリプションは追加の設定なしですぐに動作します。
バックエンドでサブスクリプションプロシージャを定義する方法については、tRPC API ジェネレーターガイドを参照してください。
useSubscription フックの使用
Section titled “useSubscription フックの使用”オプションプロキシの subscriptionOptions を使用して、useSubscription フックでサブスクリプションを利用できます:
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> );}subscription オブジェクトは以下を提供します:
subscription.data— 最後に受信したデータsubscription.error— 最後に受信したエラーsubscription.status—'idle'、'connecting'、'pending'、または'error'のいずれかsubscription.reset()— サブスクリプションをリセット (エラーからの回復に便利)
バニラクライアントの使用
Section titled “バニラクライアントの使用”または、use<ApiName>Client フックを介してバニラ tRPC クライアントを使用して、サブスクリプションのライフサイクルをより細かく制御できます:
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> );}ベストプラクティス
Section titled “ベストプラクティス”ローディング状態の処理
Section titled “ローディング状態の処理”より良いユーザーエクスペリエンスのために、常にローディングとエラー状態を処理してください:
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> );}より良いユーザーエクスペリエンスのために楽観的更新を使用してください:
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> );}データのプリフェッチ
Section titled “データのプリフェッチ”より良いパフォーマンスのためにデータをプリフェッチしてください:
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> );}無限クエリでページネーションを処理してください:
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> );}無限クエリは、cursor という名前の入力プロパティを持つプロシージャにのみ使用できることに注意することが重要です。
統合は完全なエンドツーエンドの型安全性を提供します。IDE は、すべての 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>;}型はバックエンドのルーターとスキーマ定義から自動的に推論されるため、API への変更はビルドする必要なく、フロントエンドコードに即座に反映されます。
カスタム認証
Section titled “カスタム認証”tRPC API が Custom 認証 (Lambda Authorizer) を使用している場合、生成されたクライアントプロバイダーには、オーソライザーが期待する認証ヘッダーを追加する必要があるプレースホルダー headers が含まれています。生成された <ApiName>ClientProvider.tsx の // TODO: Add headers required by your custom authorizer コメントを探し、トークンまたは API キーのロジックに置き換えてください。
詳細については、以下を参照してください: