メインコンテンツにスキップ
バージョン:11.x

useUtils

useUtilsは、@trpc/react-queryを介して実行するクエリのキャッシュされたデータを管理するためのヘルパーへのアクセスを提供するフックです。これらのヘルパーは実際には、@tanstack/react-queryqueryClientメソッドを薄くラップしたものです。ここで提供する以上のuseContextヘルパーのオプションと使用パターンに関する詳細情報が必要な場合は、それぞれの@tanstack/react-queryドキュメントにリンクしているので、それらを参照してください。

このフックは、10.41.0までuseContext()と呼ばれていました(そして、当面の間はエイリアスとして残ります)

使い方

useUtilsは、ルーターにあるすべての利用可能なクエリを持つオブジェクトを返します。これはtrpcクライアントオブジェクトと同じように使用します。クエリに到達すると、クエリヘルパーにアクセスできるようになります。たとえば、allクエリを持つpostルーターがあるとしましょう。

server.ts
ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
 
const t = initTRPC.create();
 
const appRouter = t.router({
post: t.router({
all: t.procedure.query(() => {
return {
posts: [
{ id: 1, title: 'everlong' },
{ id: 2, title: 'After Dark' },
],
};
}),
}),
});
 
export type AppRouter = typeof appRouter;
server.ts
ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
 
const t = initTRPC.create();
 
const appRouter = t.router({
post: t.router({
all: t.procedure.query(() => {
return {
posts: [
{ id: 1, title: 'everlong' },
{ id: 2, title: 'After Dark' },
],
};
}),
}),
});
 
export type AppRouter = typeof appRouter;

コンポーネント内で、useUtilsが返すオブジェクトをナビゲートし、post.allクエリに到達すると、クエリヘルパーにアクセスできます!

MyComponent.tsx
tsx
function MyComponent() {
const utils = trpc.useUtils();
utils.post.all.f;
                  
// [...]
}
MyComponent.tsx
tsx
function MyComponent() {
const utils = trpc.useUtils();
utils.post.all.f;
                  
// [...]
}

ヘルパー

これらはuseUtilsを介してアクセスできるヘルパーです。以下の表は、どのtRPCヘルパーがどの@tanstack/react-queryヘルパーメソッドをラップしているかを知るのに役立ちます。各react-queryメソッドは、それぞれのドキュメント/ガイドにリンクされます。

tRPCヘルパーラッパー@tanstack/react-queryヘルパーメソッド
fetchqueryClient.fetchQuery
prefetchqueryClient.prefetchQuery
fetchInfinitequeryClient.fetchInfiniteQuery
prefetchInfinitequeryClient.prefetchInfiniteQuery
ensureDataqueryClient.ensureData
invalidatequeryClient.invalidateQueries
refetchqueryClient.refetchQueries
cancelqueryClient.cancelQueries
setDataqueryClient.setQueryData
setQueriesDataqueryClient.setQueriesData
getDataqueryClient.getQueryData
setInfiniteDataqueryClient.setInfiniteQueryData
getInfiniteDataqueryClient.getInfiniteData

❓ 必要な関数がここにありません!

@tanstack/react-queryには、まだtRPCコンテキストに組み込んでいない多くの関数があります。ここにない関数が必要な場合は、お気軽に機能リクエストを開いてリクエストしてください。

それまでの間、@tanstack/react-queryから関数を直接インポートして使用できます。また、これらの関数を使用する際にフィルターで正しいqueryKeyを取得するために使用できるgetQueryKeyも提供しています。

プロキシクライアント

上記のreact-queryヘルパーに加えて、コンテキストはtRPCプロキシクライアントも公開します。これにより、追加のバニラクライアントを作成しなくても、async/awaitを使用してプロシージャを呼び出すことができます。

tsx
import { trpc } from '../utils/trpc';
function MyComponent() {
const [apiKey, setApiKey] = useState();
const utils = trpc.useUtils();
return (
<Form
handleSubmit={async (event) => {
const apiKey = await utils.client.apiKey.create.mutate(event);
setApiKey(apiKey);
}}
>
...
</Form>
);
}
tsx
import { trpc } from '../utils/trpc';
function MyComponent() {
const [apiKey, setApiKey] = useState();
const utils = trpc.useUtils();
return (
<Form
handleSubmit={async (event) => {
const apiKey = await utils.client.apiKey.create.mutate(event);
setApiKey(apiKey);
}}
>
...
</Form>
);
}

クエリの無効化

invalidateヘルパーを介してクエリを無効化します。invalidateは、他のヘルパーとは異なり、ルーターマップのすべてのレベルで利用できるという点で、実際には特別なヘルパーです。これは、単一のクエリ、ルーター全体、または必要に応じてすべてのルーターでinvalidateを実行できることを意味します。以下のセクションで詳しく説明します。

単一のクエリの無効化

単一のプロシージャに関連するクエリを無効化し、バックエンドへの不必要な呼び出しを防ぐために渡された入力に基づいてフィルターすることもできます。

コード例

tsx
import { trpc } from '../utils/trpc';
function MyComponent() {
const utils = trpc.useUtils();
const mutation = trpc.post.edit.useMutation({
onSuccess(input) {
utils.post.all.invalidate();
utils.post.byId.invalidate({ id: input.id }); // Will not invalidate queries for other id's 👍
},
});
// [...]
}
tsx
import { trpc } from '../utils/trpc';
function MyComponent() {
const utils = trpc.useUtils();
const mutation = trpc.post.edit.useMutation({
onSuccess(input) {
utils.post.all.invalidate();
utils.post.byId.invalidate({ id: input.id }); // Will not invalidate queries for other id's 👍
},
});
// [...]
}

ルーター全体での無効化

1つのクエリだけでなく、ルーター全体でクエリを無効化することもできます。

コード例

バックエンドコード
server/routers/_app.ts
tsx
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
export const t = initTRPC.create();
export const appRouter = t.router({
// sub Post router
post: t.router({
all: t.procedure.query(() => {
return {
posts: [
{ id: 1, title: 'everlong' },
{ id: 2, title: 'After Dark' },
],
};
}),
byId: t.procedure
.input(
z.object({
id: z.string(),
}),
)
.query(({ input }) => {
return {
post: { id: input?.id, title: 'Look me up!' },
};
}),
edit: t.procedure
.input(z.object({ id: z.number(), title: z.string() }))
.mutation(({ input }) => {
return { post: { id: input.id, title: input.title } };
}),
}),
// separate user router
user: t.router({
all: t.procedure.query(() => {
return { users: [{ name: 'Dave Grohl' }, { name: 'Haruki Murakami' }] };
}),
}),
});
server/routers/_app.ts
tsx
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
export const t = initTRPC.create();
export const appRouter = t.router({
// sub Post router
post: t.router({
all: t.procedure.query(() => {
return {
posts: [
{ id: 1, title: 'everlong' },
{ id: 2, title: 'After Dark' },
],
};
}),
byId: t.procedure
.input(
z.object({
id: z.string(),
}),
)
.query(({ input }) => {
return {
post: { id: input?.id, title: 'Look me up!' },
};
}),
edit: t.procedure
.input(z.object({ id: z.number(), title: z.string() }))
.mutation(({ input }) => {
return { post: { id: input.id, title: input.title } };
}),
}),
// separate user router
user: t.router({
all: t.procedure.query(() => {
return { users: [{ name: 'Dave Grohl' }, { name: 'Haruki Murakami' }] };
}),
}),
});
tsx
import { trpc } from '../utils/trpc';
function MyComponent() {
const utils = trpc.useUtils();
const invalidateAllQueriesAcrossAllRouters = () => {
// 1️⃣
// All queries on all routers will be invalidated 🔥
utils.invalidate();
};
const invalidateAllPostQueries = () => {
// 2️⃣
// All post queries will be invalidated 📭
utils.post.invalidate();
};
const invalidatePostById = () => {
// 3️⃣
// All queries in the post router with input {id:1} invalidated 📭
utils.post.byId.invalidate({ id: 1 });
};
// Example queries
trpc.user.all.useQuery(); // Would only be validated by 1️⃣ only.
trpc.post.all.useQuery(); // Would be invalidated by 1️⃣ & 2️⃣
trpc.post.byId.useQuery({ id: 1 }); // Would be invalidated by 1️⃣, 2️⃣ and 3️⃣
trpc.post.byId.useQuery({ id: 2 }); // would be invalidated by 1️⃣ and 2️⃣ but NOT 3️⃣!
// [...]
}
tsx
import { trpc } from '../utils/trpc';
function MyComponent() {
const utils = trpc.useUtils();
const invalidateAllQueriesAcrossAllRouters = () => {
// 1️⃣
// All queries on all routers will be invalidated 🔥
utils.invalidate();
};
const invalidateAllPostQueries = () => {
// 2️⃣
// All post queries will be invalidated 📭
utils.post.invalidate();
};
const invalidatePostById = () => {
// 3️⃣
// All queries in the post router with input {id:1} invalidated 📭
utils.post.byId.invalidate({ id: 1 });
};
// Example queries
trpc.user.all.useQuery(); // Would only be validated by 1️⃣ only.
trpc.post.all.useQuery(); // Would be invalidated by 1️⃣ & 2️⃣
trpc.post.byId.useQuery({ id: 1 }); // Would be invalidated by 1️⃣, 2️⃣ and 3️⃣
trpc.post.byId.useQuery({ id: 2 }); // would be invalidated by 1️⃣ and 2️⃣ but NOT 3️⃣!
// [...]
}

すべてのミューテーションでフルキャッシュを無効化

ミューテーションでどのクエリを無効化する必要があるかを正確に追跡することは難しいため、すべてのミューテーションの副作用としてフルキャッシュを無効化することは実用的な解決策となる場合があります。リクエストバッチ処理があるため、この無効化は、表示しているページのすべてのクエリを1つのリクエストで再フェッチするだけです。

この問題を解決するための機能を追加しました

ts
export const trpc = createTRPCReact<AppRouter, SSRContext>({
overrides: {
useMutation: {
/**
* This function is called whenever a `.useMutation` succeeds
**/
async onSuccess(opts) {
/**
* @note that order here matters:
* The order here allows route changes in `onSuccess` without
* having a flash of content change whilst redirecting.
**/
// Calls the `onSuccess` defined in the `useQuery()`-options:
await opts.originalFn();
// Invalidate all queries in the react-query cache:
await opts.queryClient.invalidateQueries();
},
},
},
});
ts
export const trpc = createTRPCReact<AppRouter, SSRContext>({
overrides: {
useMutation: {
/**
* This function is called whenever a `.useMutation` succeeds
**/
async onSuccess(opts) {
/**
* @note that order here matters:
* The order here allows route changes in `onSuccess` without
* having a flash of content change whilst redirecting.
**/
// Calls the `onSuccess` defined in the `useQuery()`-options:
await opts.originalFn();
// Invalidate all queries in the react-query cache:
await opts.queryClient.invalidateQueries();
},
},
},
});

追加オプション

クエリヘルパーに加えて、useUtilsが返すオブジェクトには次のプロパティも含まれています。

ts
interface ProxyTRPCContextProps<TRouter extends AnyRouter, TSSRContext> {
/**
* The `TRPCClient`
*/
client: TRPCClient<TRouter>;
/**
* The SSR context when server-side rendering
* @default null
*/
ssrContext?: TSSRContext | null;
/**
* State of SSR hydration.
* - `false` if not using SSR.
* - `prepass` when doing a prepass to fetch queries' data
* - `mounting` before TRPCProvider has been rendered on the client
* - `mounted` when the TRPCProvider has been rendered on the client
* @default false
*/
ssrState?: SSRState;
/**
* Abort loading query calls when unmounting a component - usually when navigating to a new page
* @default false
*/
abortOnUnmount?: boolean;
}
ts
interface ProxyTRPCContextProps<TRouter extends AnyRouter, TSSRContext> {
/**
* The `TRPCClient`
*/
client: TRPCClient<TRouter>;
/**
* The SSR context when server-side rendering
* @default null
*/
ssrContext?: TSSRContext | null;
/**
* State of SSR hydration.
* - `false` if not using SSR.
* - `prepass` when doing a prepass to fetch queries' data
* - `mounting` before TRPCProvider has been rendered on the client
* - `mounted` when the TRPCProvider has been rendered on the client
* @default false
*/
ssrState?: SSRState;
/**
* Abort loading query calls when unmounting a component - usually when navigating to a new page
* @default false
*/
abortOnUnmount?: boolean;
}