Skip to content

React から tRPC へ

Nx Plugin for AWS は、tRPC API を React ウェブサイトと素早く統合するためのジェネレーターを提供します。AWS IAM と Cognito 認証のサポート、適切なエラーハンドリングを含む、tRPC バックエンドへの接続に必要なすべての設定をセットアップします。この統合により、フロントエンドと tRPC バックエンド間で完全なエンドツーエンドの型安全性が提供されます。

このジェネレーターを使用する前に、React アプリケーションが以下を備えていることを確認してください:

  1. アプリケーションをレンダリングする main.tsx ファイル
  2. tRPC プロバイダーが自動的に注入される <App/> JSX 要素
  3. 動作する tRPC API (tRPC API ジェネレーターを使用して生成)
  4. 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>,
);
Terminal window
pnpm nx g @aws/nx-plugin:connection
変更されるファイルを確認するためにドライランを実行することもできます
Terminal window
pnpm nx g @aws/nx-plugin:connection --dry-run
パラメータデフォルト説明
sourceProject 必須string-ソース プロジェクト
targetProject 必須string-接続先のターゲット プロジェクト
sourceComponent string-接続元のソース コンポーネント (コンポーネント名、ソース プロジェクト ルートからの相対パス、またはジェネレーター ID)。プロジェクトをソースとして明示的に選択するには '.' を使用します。
targetComponent string-接続先のターゲット コンポーネント (コンポーネント名、ターゲット プロジェクト ルートからの相対パス、またはジェネレーター ID)。プロジェクトをターゲットとして明示的に選択するには '.' を使用します。
preferInstallDependencies booleantrueジェネレーター実行後に依存関係のインストールを優先するかどうか。複数のジェネレーターをバッチ処理する際にインストールを延期する場合はfalseに設定します(後続のジェネレーターがNxプロジェクトグラフを計算できるよう、必要に応じてインストールは実行されます)。最後に一度だけインストールします。

ジェネレーターは、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-query
  • aws4fetch (IAM 認証を使用する場合)
  • event-source-polyfill (REST API を使用する場合、サブスクリプションサポート用)

tRPC オプションプロキシフックの使用

Section titled “tRPC オプションプロキシフックの使用”

ジェネレーターは、useQueryuseMutation などの 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>;
}

統合には、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 ジェネレーターガイドを参照してください。

オプションプロキシの 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() — サブスクリプションをリセット (エラーからの回復に便利)

または、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>
);
}

より良いユーザーエクスペリエンスのために、常にローディングとエラー状態を処理してください:

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

より良いパフォーマンスのためにデータをプリフェッチしてください:

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 への変更はビルドする必要なく、フロントエンドコードに即座に反映されます。

tRPC API が Custom 認証 (Lambda Authorizer) を使用している場合、生成されたクライアントプロバイダーには、オーソライザーが期待する認証ヘッダーを追加する必要があるプレースホルダー headers が含まれています。生成された <ApiName>ClientProvider.tsx// TODO: Add headers required by your custom authorizer コメントを探し、トークンまたは API キーのロジックに置き換えてください。

詳細については、以下を参照してください: