본문 바로가기
버전: 10.x

useUtils

비공식 베타 번역

이 페이지는 PageTurner AI로 번역되었습니다(베타). 프로젝트 공식 승인을 받지 않았습니다. 오류를 발견하셨나요? 문제 신고 →

useUtils@trpc/react-query를 통해 실행한 쿼리의 캐시된 데이터를 관리할 수 있도록 해주는 헬퍼 함수에 접근할 수 있는 훅입니다. 이 헬퍼들은 실제로 @tanstack/react-queryqueryClient 메서드를 감싼 얇은 래퍼 레이어입니다. 여기서 설명하는 것보다 useUtils 헬퍼의 옵션 및 사용 패턴에 대한 더 깊이 있는 정보가 필요하시다면, 해당 @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 helper wrapper@tanstack/react-query helper method
fetchqueryClient.fetchQuery
prefetchqueryClient.prefetchQuery
fetchInfinitequeryClient.fetchInfiniteQuery
prefetchInfinitequeryClient.prefetchInfiniteQuery
ensureDataqueryClient.ensureData
invalidatequeryClient.invalidateQueries
refetchqueryClient.refetchQueries
cancelqueryClient.cancelQueries
setDataqueryClient.setQueryData
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 👍
},
});
// [...]
}

라우터 전체 무효화

단일 쿼리가 아닌 전체 라우터에 걸쳐 쿼리를 무효화할 수도 있습니다.

예제 코드

Backend code
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
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;
}