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

TanStack React Query

빠른 예제 쿼리

tsx
import { useQuery } from '@tanstack/react-query';
import { useTRPC } from './trpc';
 
function Users() {
const trpc = useTRPC();
 
const greetingQuery = useQuery(trpc.greeting.queryOptions({ name: 'Jerry' }));
 
// greetingQuery.data === 'Hello Jerry'
}
tsx
import { useQuery } from '@tanstack/react-query';
import { useTRPC } from './trpc';
 
function Users() {
const trpc = useTRPC();
 
const greetingQuery = useQuery(trpc.greeting.queryOptions({ name: 'Jerry' }));
 
// greetingQuery.data === 'Hello Jerry'
}

사용법

이 클라이언트는 TanStack React Query의 사용 방식을 그대로 따르는 가볍고 타입 안전한 팩토리를 제공하는 것을 목표로 합니다. 자동완성을 따라 사용하면 TanStack React Query 문서의 지식을 그대로 활용하면서 개발에 집중할 수 있습니다.

tsx
export default function Basics() {
const trpc = useTRPC();
const queryClient = useQueryClient();
 
// Create QueryOptions which can be passed to query hooks
const myQueryOptions = trpc.path.to.query.queryOptions({ /** inputs */ })
const myQuery = useQuery(myQueryOptions)
// or:
// useSuspenseQuery(myQueryOptions)
// useInfiniteQuery(myQueryOptions)
 
// Create MutationOptions which can be passed to useMutation
const myMutationOptions = trpc.path.to.mutation.mutationOptions()
const myMutation = useMutation(myMutationOptions)
 
// Create a QueryKey which can be used to manipulate many methods
// on TanStack's QueryClient in a type-safe manner
const myQueryKey = trpc.path.to.query.queryKey()
 
const invalidateMyQueryKey = () => {
queryClient.invalidateQueries({ queryKey: myQueryKey })
}
 
return (
// Your app here
null
)
}
tsx
export default function Basics() {
const trpc = useTRPC();
const queryClient = useQueryClient();
 
// Create QueryOptions which can be passed to query hooks
const myQueryOptions = trpc.path.to.query.queryOptions({ /** inputs */ })
const myQuery = useQuery(myQueryOptions)
// or:
// useSuspenseQuery(myQueryOptions)
// useInfiniteQuery(myQueryOptions)
 
// Create MutationOptions which can be passed to useMutation
const myMutationOptions = trpc.path.to.mutation.mutationOptions()
const myMutation = useMutation(myMutationOptions)
 
// Create a QueryKey which can be used to manipulate many methods
// on TanStack's QueryClient in a type-safe manner
const myQueryKey = trpc.path.to.query.queryKey()
 
const invalidateMyQueryKey = () => {
queryClient.invalidateQueries({ queryKey: myQueryKey })
}
 
return (
// Your app here
null
)
}

trpc 객체는 완전히 타입 안전하며 AppRouter의 모든 프로시저에 대한 자동완성을 제공합니다. 프록시 끝에 다음 메서드들이 사용할 수 있습니다:

queryOptions - 데이터 쿼리

모든 쿼리 프로시저에서 사용할 수 있습니다. TanStack의 queryOptions 함수를 감싸는 타입 안전한 래퍼를 제공합니다. 첫 번째 인수는 프로시저의 입력이며, 두 번째 인수에는 네이티브 TanStack React Query 옵션을 모두 전달할 수 있습니다.

ts
const queryOptions = trpc.path.to.query.queryOptions(
{
/** input */
id: 'foo',
},
{
// Any Tanstack React Query options
staleTime: 1000,
},
);
ts
const queryOptions = trpc.path.to.query.queryOptions(
{
/** input */
id: 'foo',
},
{
// Any Tanstack React Query options
staleTime: 1000,
},
);

클라이언트에 tRPC 요청 옵션을 제공하기 위해 queryOptions 함수에 trpc 객체를 추가로 제공할 수 있습니다.

ts
const queryOptions = trpc.path.to.query.queryOptions(
{
/** input */
id: 'foo',
},
{
trpc: {
// Provide tRPC request options to the client
context: {
// see https://trpc.io/docs/client/links#managing-context
},
},
},
);
ts
const queryOptions = trpc.path.to.query.queryOptions(
{
/** input */
id: 'foo',
},
{
trpc: {
// Provide tRPC request options to the client
context: {
// see https://trpc.io/docs/client/links#managing-context
},
},
},
);

쿼리를 타입 안전하게 비활성화하려면 skipToken을 사용할 수 있습니다:

ts
const query = useQuery(
trpc.user.details.queryOptions(
user?.id && project?.id
? {
userId: user.id,
projectId: project.id,
}
: skipToken,
{
staleTime: 1000,
},
),
);
ts
const query = useQuery(
trpc.user.details.queryOptions(
user?.id && project?.id
? {
userId: user.id,
projectId: project.id,
}
: skipToken,
{
staleTime: 1000,
},
),
);

결과는 useQuery 또는 useSuspenseQuery 훅이나 fetchQuery, prefetchQuery, prefetchInfiniteQuery, invalidateQueries 등 쿼리 클라이언트 메서드에 전달할 수 있습니다.

infiniteQueryOptions - 무한 데이터 쿼리

커서 입력을 받는 모든 쿼리 프로시저에서 사용할 수 있습니다. TanStack의 infiniteQueryOptions 함수를 감싸는 타입 안전한 래퍼를 제공합니다. 첫 번째 인수는 프로시저의 입력이며, 두 번째 인수에는 네이티브 TanStack React Query 옵션을 모두 전달할 수 있습니다.

ts
const infiniteQueryOptions = trpc.path.to.query.infiniteQueryOptions(
{
/** input */
},
{
// Any Tanstack React Query options
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
},
);
ts
const infiniteQueryOptions = trpc.path.to.query.infiniteQueryOptions(
{
/** input */
},
{
// Any Tanstack React Query options
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
},
);

queryKey - 쿼리 키 가져오기 및 쿼리 클라이언트에서 작업 수행

모든 쿼리 프로시저에서 사용할 수 있습니다. 쿼리 키에 타입 안전하게 접근할 수 있습니다.

ts
const queryKey = trpc.path.to.query.queryKey();
ts
const queryKey = trpc.path.to.query.queryKey();

TanStack React Query는 쿼리 키에 퍼지 매칭을 사용하므로, 하위 경로의 부분 쿼리 키를 만들어 라우터에 속한 모든 쿼리와 일치시킬 수도 있습니다:

ts
const queryKey = trpc.router.pathKey();
ts
const queryKey = trpc.router.pathKey();

또는 모든 tRPC 쿼리를 일치시키기 위해 루트 경로를 사용할 수도 있습니다:

ts
const queryKey = trpc.pathKey();
ts
const queryKey = trpc.pathKey();

infiniteQueryKey - 무한 쿼리 키 가져오기

커서 입력을 받는 모든 쿼리 프로시저에서 사용할 수 있습니다. 무한 쿼리의 쿼리 키에 타입 안전하게 접근할 수 있습니다.

ts
const infiniteQueryKey = trpc.path.to.query.infiniteQueryKey({
/** input */
});
ts
const infiniteQueryKey = trpc.path.to.query.infiniteQueryKey({
/** input */
});

결과는 getQueryData, setQueryData, invalidateQueries 등 쿼리 클라이언트 메서드와 함께 사용할 수 있습니다.

ts
// Get cached data for an infinite query
const cachedData = queryClient.getQueryData(
trpc.path.to.query.infiniteQueryKey({ cursor: 0 }),
);
 
// Set cached data for an infinite query
queryClient.setQueryData(
trpc.path.to.query.infiniteQueryKey({ cursor: 0 }),
(data) => {
// Modify the data
return data;
},
);
ts
// Get cached data for an infinite query
const cachedData = queryClient.getQueryData(
trpc.path.to.query.infiniteQueryKey({ cursor: 0 }),
);
 
// Set cached data for an infinite query
queryClient.setQueryData(
trpc.path.to.query.infiniteQueryKey({ cursor: 0 }),
(data) => {
// Modify the data
return data;
},
);

queryFilter - 쿼리 필터 생성

모든 쿼리 프로시저에서 사용할 수 있습니다. 쿼리 필터를 타입 안전하게 생성할 수 있습니다.

ts
const queryFilter = trpc.path.to.query.queryFilter(
{
/** input */
},
{
// Any Tanstack React Query filter
predicate: (query) => {
return !!query.state.data;
},
},
);
ts
const queryFilter = trpc.path.to.query.queryFilter(
{
/** input */
},
{
// Any Tanstack React Query filter
predicate: (query) => {
return !!query.state.data;
},
},
);

쿼리 키와 마찬가지로, 라우터 전체에 걸쳐 필터를 실행하려면 pathFilter를 사용하여 하위 경로를 지정할 수 있습니다.

ts
const queryFilter = trpc.path.pathFilter({
// Any Tanstack React Query filter
predicate: (query) => {
return !!query.state.data;
},
});
ts
const queryFilter = trpc.path.pathFilter({
// Any Tanstack React Query filter
predicate: (query) => {
return !!query.state.data;
},
});

queryClient.invalidateQueries 등 클라이언트 메서드에 전달할 수 있는 필터를 생성하는 데 유용합니다.

infiniteQueryFilter - 무한 쿼리 필터 생성

커서 입력을 받는 모든 쿼리 프로시저에서 사용할 수 있습니다. 무한 쿼리에 대한 쿼리 필터를 타입 안전하게 생성할 수 있습니다.

ts
const infiniteQueryFilter = trpc.path.to.query.infiniteQueryFilter(
{
/** input */
},
{
// Any Tanstack React Query filter
predicate: (query) => {
return !!query.state.data;
},
},
);
ts
const infiniteQueryFilter = trpc.path.to.query.infiniteQueryFilter(
{
/** input */
},
{
// Any Tanstack React Query filter
predicate: (query) => {
return !!query.state.data;
},
},
);

queryClient.invalidateQueries 등 클라이언트 메서드에 전달할 수 있는 필터를 생성하는 데 유용합니다.

ts
await queryClient.invalidateQueries(
trpc.path.to.query.infiniteQueryFilter(
{},
{
predicate: (query) => {
// Filter logic based on query state
return query.state.status === 'success';
},
},
),
);
ts
await queryClient.invalidateQueries(
trpc.path.to.query.infiniteQueryFilter(
{},
{
predicate: (query) => {
// Filter logic based on query state
return query.state.status === 'success';
},
},
),
);

mutationOptions - 뮤테이션 옵션 생성

모든 뮤테이션 프로시저에서 사용할 수 있습니다. useMutation에 전달할 수 있는 옵션을 구성하기 위한 타입 안전한 항등 함수를 제공합니다.

ts
const mutationOptions = trpc.path.to.mutation.mutationOptions({
// Any Tanstack React Query options
onSuccess: (data) => {
// do something with the data
},
});
ts
const mutationOptions = trpc.path.to.mutation.mutationOptions({
// Any Tanstack React Query options
onSuccess: (data) => {
// do something with the data
},
});

mutationKey - 뮤테이션 키 가져오기

모든 뮤테이션 프로시저에서 사용할 수 있습니다. 뮤테이션 키를 타입 안전하게 가져올 수 있습니다.

ts
const mutationKey = trpc.path.to.mutation.mutationKey();
ts
const mutationKey = trpc.path.to.mutation.mutationKey();

subscriptionOptions - 구독 옵션 생성

TanStack은 구독 훅을 제공하지 않으므로, 표준 tRPC 구독 설정과 함께 작동하는 자체 추상화를 계속 노출합니다. 모든 구독 프로시저에서 사용할 수 있습니다. useSubscription에 전달할 수 있는 옵션을 구성하기 위한 타입 안전한 항등 함수를 제공합니다. 구독을 사용하려면 tRPC 클라이언트에 httpSubscriptionLink 또는 wsLink 중 하나가 설정되어 있어야 합니다.

tsx
function SubscriptionExample() {
const trpc = useTRPC();
const subscription = useSubscription(
trpc.path.to.subscription.subscriptionOptions(
{
/** input */
},
{
enabled: true,
onStarted: () => {
// do something when the subscription is started
},
onData: (data) => {
// you can handle the data here
},
onError: (error) => {
// you can handle the error here
},
onConnectionStateChange: (state) => {
// you can handle the connection state here
},
},
),
);
 
// Or you can handle the state here
subscription.data; // The lastly received data
subscription.error; // The lastly received error
 
/**
* The current status of the subscription.
* Will be one of: `'idle'`, `'connecting'`, `'pending'`, or `'error'`.
*
* - `idle`: subscription is disabled or ended
* - `connecting`: trying to establish a connection
* - `pending`: connected to the server, receiving data
* - `error`: an error occurred and the subscription is stopped
*/
subscription.status;
 
// Reset the subscription (if you have an error etc)
subscription.reset();
 
return <>{/* ... */}</>;
}
tsx
function SubscriptionExample() {
const trpc = useTRPC();
const subscription = useSubscription(
trpc.path.to.subscription.subscriptionOptions(
{
/** input */
},
{
enabled: true,
onStarted: () => {
// do something when the subscription is started
},
onData: (data) => {
// you can handle the data here
},
onError: (error) => {
// you can handle the error here
},
onConnectionStateChange: (state) => {
// you can handle the connection state here
},
},
),
);
 
// Or you can handle the state here
subscription.data; // The lastly received data
subscription.error; // The lastly received error
 
/**
* The current status of the subscription.
* Will be one of: `'idle'`, `'connecting'`, `'pending'`, or `'error'`.
*
* - `idle`: subscription is disabled or ended
* - `connecting`: trying to establish a connection
* - `pending`: connected to the server, receiving data
* - `error`: an error occurred and the subscription is stopped
*/
subscription.status;
 
// Reset the subscription (if you have an error etc)
subscription.reset();
 
return <>{/* ... */}</>;
}

쿼리 키 접두사 추가

단일 애플리케이션에서 여러 tRPC 프로바이더를 사용하는 경우(예: 다른 백엔드 서비스에 연결하는 경우), 동일한 경로를 가진 쿼리가 캐시에서 충돌할 수 있습니다. 쿼리 키 접두사 추가를 활성화하여 이를 방지할 수 있습니다.

tsx
// Without prefixes - these would collide!
const authQuery = useQuery(trpcAuth.list.queryOptions()); // auth service
const billingQuery = useQuery(trpcBilling.list.queryOptions()); // billing service
tsx
// Without prefixes - these would collide!
const authQuery = useQuery(trpcAuth.list.queryOptions()); // auth service
const billingQuery = useQuery(trpcBilling.list.queryOptions()); // billing service

컨텍스트를 생성할 때 기능 플래그를 활성화합니다:

utils/trpc.ts
tsx
// [...]
 
const billing = createTRPCContext<BillingRouter, { keyPrefix: true }>();
export const BillingProvider = billing.TRPCProvider;
export const useBilling = billing.useTRPC;
export const createBillingClient = () =>
createTRPCClient<BillingRouter>({
links: [
/* ... */
],
});
 
const account = createTRPCContext<AccountRouter, { keyPrefix: true }>();
export const AccountProvider = account.TRPCProvider;
export const useAccount = account.useTRPC;
export const createAccountClient = () =>
createTRPCClient<AccountRouter>({
links: [
/* ... */
],
});
utils/trpc.ts
tsx
// [...]
 
const billing = createTRPCContext<BillingRouter, { keyPrefix: true }>();
export const BillingProvider = billing.TRPCProvider;
export const useBilling = billing.useTRPC;
export const createBillingClient = () =>
createTRPCClient<BillingRouter>({
links: [
/* ... */
],
});
 
const account = createTRPCContext<AccountRouter, { keyPrefix: true }>();
export const AccountProvider = account.TRPCProvider;
export const useAccount = account.useTRPC;
export const createAccountClient = () =>
createTRPCClient<AccountRouter>({
links: [
/* ... */
],
});
App.tsx
tsx
import { useState } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import {
BillingProvider,
AccountProvider,
createBillingClient,
createAccountClient,
} from './utils/trpc';
 
// [...]
 
export function App() {
const [queryClient] = useState(() => new QueryClient());
const [billingClient] = useState(() => createBillingClient());
const [accountClient] = useState(() => createAccountClient());
 
return (
<QueryClientProvider client={queryClient}>
<BillingProvider
trpcClient={billingClient}
queryClient={queryClient}
keyPrefix="billing"
>
<AccountProvider
trpcClient={accountClient}
queryClient={queryClient}
keyPrefix="account"
>
<div>{/* ... */}</div>
</AccountProvider>
</BillingProvider>
</QueryClientProvider>
);
}
App.tsx
tsx
import { useState } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import {
BillingProvider,
AccountProvider,
createBillingClient,
createAccountClient,
} from './utils/trpc';
 
// [...]
 
export function App() {
const [queryClient] = useState(() => new QueryClient());
const [billingClient] = useState(() => createBillingClient());
const [accountClient] = useState(() => createAccountClient());
 
return (
<QueryClientProvider client={queryClient}>
<BillingProvider
trpcClient={billingClient}
queryClient={queryClient}
keyPrefix="billing"
>
<AccountProvider
trpcClient={accountClient}
queryClient={queryClient}
keyPrefix="account"
>
<div>{/* ... */}</div>
</AccountProvider>
</BillingProvider>
</QueryClientProvider>
);
}
components/MyComponent.tsx
tsx
import { useQuery } from '@tanstack/react-query';
import { useBilling, useAccount } from '../utils/trpc';
 
// [...]
 
export function MyComponent() {
const billing = useBilling();
const account = useAccount();
 
const billingList = useQuery(billing.list.queryOptions());
const accountList = useQuery(account.list.queryOptions());
 
return (
<div>
<div>Billing: {JSON.stringify(billingList.data ?? null)}</div>
<div>Account: {JSON.stringify(accountList.data ?? null)}</div>
</div>
);
}
components/MyComponent.tsx
tsx
import { useQuery } from '@tanstack/react-query';
import { useBilling, useAccount } from '../utils/trpc';
 
// [...]
 
export function MyComponent() {
const billing = useBilling();
const account = useAccount();
 
const billingList = useQuery(billing.list.queryOptions());
const accountList = useQuery(account.list.queryOptions());
 
return (
<div>
<div>Billing: {JSON.stringify(billingList.data ?? null)}</div>
<div>Account: {JSON.stringify(accountList.data ?? null)}</div>
</div>
);
}

쿼리 키에 접두사가 올바르게 추가되어 충돌을 방지합니다:

tsx
// Example of how the query keys look with prefixes
const queryKeys = [
[['billing'], ['list'], { type: 'query' }],
[['account'], ['list'], { type: 'query' }],
];
tsx
// Example of how the query keys look with prefixes
const queryKeys = [
[['billing'], ['list'], { type: 'query' }],
[['account'], ['list'], { type: 'query' }],
];

입력 및 출력 타입 추론

프로시저 또는 라우터의 입력과 출력 타입을 추론해야 하는 경우, 상황에 따라 두 가지 옵션이 있습니다.

전체 라우터의 입력 및 출력 타입 추론

ts
import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server';
import type { AppRouter } from './server/router';
 
export type Inputs = inferRouterInputs<AppRouter>;
export type Outputs = inferRouterOutputs<AppRouter>;
ts
import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server';
import type { AppRouter } from './server/router';
 
export type Inputs = inferRouterInputs<AppRouter>;
export type Outputs = inferRouterOutputs<AppRouter>;

단일 프로시저의 타입 추론

ts
import type { inferInput, inferOutput } from '@trpc/tanstack-react-query';
 
function Component() {
const trpc = useTRPC();
 
type Input = inferInput<typeof trpc.path.to.procedure>;
type Output = inferOutput<typeof trpc.path.to.procedure>;
}
ts
import type { inferInput, inferOutput } from '@trpc/tanstack-react-query';
 
function Component() {
const trpc = useTRPC();
 
type Input = inferInput<typeof trpc.path.to.procedure>;
type Output = inferOutput<typeof trpc.path.to.procedure>;
}

tRPC 클라이언트 접근

React Context를 사용한 설정을 사용했다면, useTRPCClient 훅을 사용하여 tRPC 클라이언트에 접근할 수 있습니다.

tsx
import { useTRPCClient } from './trpc';
 
async function Component() {
const trpcClient = useTRPCClient();
 
const result = await trpcClient.getUser.query({
id: '1',
});
}
tsx
import { useTRPCClient } from './trpc';
 
async function Component() {
const trpcClient = useTRPCClient();
 
const result = await trpcClient.getUser.query({
id: '1',
});
}

React Context 없이 설정한 경우, 대신 전역 클라이언트 인스턴스를 직접 가져올 수 있습니다.

ts
import { client } from './trpc';
 
const result = await client.path.to.procedure.query({
/** input */
id: 'foo',
});
ts
import { client } from './trpc';
 
const result = await client.path.to.procedure.query({
/** input */
id: 'foo',
});