본문으로 건너뛰기
버전: 11.x

useUtils

useUtils@trpc/react-query로 실행한 쿼리의 캐시 데이터를 관리하는 헬퍼를 제공하는 훅입니다. 각 헬퍼는 @tanstack/react-queryqueryClient 메서드를 감싼 래퍼입니다. useUtils 헬퍼의 세부 옵션과 사용 패턴은 각 헬퍼에 연결된 @tanstack/react-query 문서를 참조하세요.

노트

이 훅은 10.41.0까지 useContext()라는 이름이었으며, 당분간 별칭으로도 유지됩니다.

사용법

useUtils는 라우터의 모든 쿼리에 대한 헬퍼를 담은 객체를 반환합니다. trpc 클라이언트 객체와 같은 방식으로 경로를 따라가면 각 쿼리의 헬퍼에 접근할 수 있습니다. 예를 들어 all 쿼리가 있는 post 라우터를 살펴보겠습니다:

server.ts
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
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
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from './server';
 
const trpc = createTRPCReact<AppRouter>();
 
function MyComponent() {
const utils = trpc.useUtils();
utils.post.all.f;
                  
}
MyComponent.tsx
tsx
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from './server';
 
const trpc = createTRPCReact<AppRouter>();
 
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
setMutationDefaultsqueryClient.setMutationDefaults
getMutationDefaultsqueryClient.getMutationDefaults
isMutatingqueryClient.isMutating
resetqueryClient.resetQueries

❓ 원하는 함수가 여기에 없어요!

@tanstack/react-query에는 아직 tRPC 컨텍스트에 포함되지 않은 많은 함수가 있습니다. 여기에 없는 함수가 필요하다면, 기능 요청을 열기를 통해 요청해 주십시오.

그동안 @tanstack/react-query에서 함수를 직접 가져와서 사용할 수 있습니다. 또한, 이러한 함수를 사용할 때 필터에서 올바른 queryKey를 가져올 수 있도록 getQueryKey도 제공합니다.

프록시 클라이언트

위 react-query 헬퍼 외에도 컨텍스트는 tRPC 프록시 클라이언트를 노출합니다. 이를 통해 추가적인 바닐라 클라이언트를 생성하지 않고도 async/await로 프로시저를 호출할 수 있습니다.

tsx
import { useState } from 'react';
import { trpc } from './utils/trpc';
 
function MyComponent() {
const [apiKey, setApiKey] = useState('');
const utils = trpc.useUtils();
 
return (
<form
onSubmit={async (event) => {
const apiKey = await utils.client.apiKey.create.mutate();
setApiKey(apiKey);
}}
>
{/* form content */}
</form>
);
}
tsx
import { useState } from 'react';
import { trpc } from './utils/trpc';
 
function MyComponent() {
const [apiKey, setApiKey] = useState('');
const utils = trpc.useUtils();
 
return (
<form
onSubmit={async (event) => {
const apiKey = await utils.client.apiKey.create.mutate();
setApiKey(apiKey);
}}
>
{/* form content */}
</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
},
});
 
// [...]
}

전체 라우터에 걸친 무효화

단일 쿼리뿐만 아니라 전체 라우터에 걸친 쿼리 무효화도 가능합니다.

예제 코드

백엔드 코드
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️⃣!
 
// [...]
}

모든 뮤테이션 시 전체 캐시 무효화

뮤테이션이 정확히 어떤 쿼리를 무효화해야 하는지 추적하는 것은 어렵기 때문에, 모든 뮤테이션의 부수 효과로 _전체 캐시_를 무효화하는 것이 실용적인 해결책이 될 수 있습니다. 요청 배치 처리가 있으므로, 이 무효화는 현재 보고 있는 페이지의 모든 쿼리를 단일 요청으로 다시 가져오게 됩니다.

이를 돕기 위해 기능을 추가했습니다:

ts
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '../server';
 
export const trpc = createTRPCReact<AppRouter>({
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
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '../server';
 
export const trpc = createTRPCReact<AppRouter>({
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;
}