@trpc/tanstack-react-query
Version:
TanStack React Query Integration for tRPC
497 lines (391 loc) • 15.9 kB
Markdown
# TanStack React Query
> The options and key factories on the trpc proxy, such as queryOptions, infiniteQueryOptions, mutationOptions, subscriptionOptions, queryKey, queryFilter and pathFilter, plus key prefixing and type inference
From https://trpc.io/docs/client/tanstack-react-query/usage
## Quick example query
```tsx no-check
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'
}
```
## Usage
The philosophy of this client is to provide thin and type-safe factories which work natively and type-safely with Tanstack React Query. This means just by following the autocompletes the client gives you, you can focus on building just with the knowledge the [TanStack React Query docs](https://tanstack.com/query/latest/docs/framework/react/overview) provide.
```tsx no-check
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
)
}
```
The `trpc` object is fully type-safe and will provide autocompletes for all the procedures in your `AppRouter`. At the end of the proxy, the following methods are available:
### `queryOptions` - querying data
Available for all query procedures. Provides a type-safe wrapper around [Tanstack's `queryOptions` function](https://tanstack.com/query/latest/docs/framework/react/reference/queryOptions). The first argument is the input for the procedure, and the second argument accepts any native Tanstack React Query options.
```ts no-check
const queryOptions = trpc.path.to.query.queryOptions(
{
/** input */
id: 'foo',
},
{
// Any Tanstack React Query options
staleTime: 1000,
},
);
```
You can additionally provide a `trpc` object to the `queryOptions` function to provide tRPC request options to the client.
```ts no-check
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
},
},
},
);
```
If you want to disable a query in a type safe way, you can use `skipToken`:
```ts no-check
const query = useQuery(
trpc.user.details.queryOptions(
user?.id && project?.id
? {
userId: user.id,
projectId: project.id,
}
: skipToken,
{
staleTime: 1000,
},
),
);
```
The result can be passed to `useQuery` or `useSuspenseQuery` hooks or query client methods like `fetchQuery`, `prefetchQuery`, `prefetchInfiniteQuery`, `invalidateQueries`, etc.
### `infiniteQueryOptions` - querying infinite data
Available for all query procedures that take a cursor input. Provides a type-safe wrapper around [Tanstack's `infiniteQueryOptions` function](https://tanstack.com/query/latest/docs/framework/react/reference/infiniteQueryOptions). The first argument is the input for the procedure, and the second argument accepts any native Tanstack React Query options.
```ts no-check
const infiniteQueryOptions = trpc.path.to.query.infiniteQueryOptions(
{
/** input */
},
{
// Any Tanstack React Query options
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
},
);
```
### `unstable_$prefetch`, `unstable_$ensure` and `unstable_$fromHandle` - passing queries as handles
On the server or in a route loader, `trpc.x.unstable_$prefetch(input)`, `await trpc.x.unstable_$ensure(input)` and `await trpc.x.unstable_$fetch(input)` start a query and return a serializable handle. Components read it with `trpc.unstable_$fromHandle(handle)`, which returns the same options as `trpc.x.queryOptions(input)`. Infinite queries have `unstable_$prefetchInfinite`, `unstable_$ensureInfinite` and `unstable_$fetchInfinite`, and their handles give the same options as `trpc.x.infiniteQueryOptions(input)`. Type props with `unstable_inferQueryHandle` and `unstable_inferInfiniteQueryHandle`.
- `unstable_$prefetch` doesn't wait. Read its handle inside a `Suspense` boundary; the data streams in.
- `unstable_$ensure` waits until the data is in the cache, so a reader using `useSuspenseQuery` needs no `Suspense` boundary.
- Handles carry no data, whichever method made them. Read them with `useSuspenseQuery` for data that's always defined.
- `unstable_$fetch` also waits, and refetches stale data before it resolves.
- Methods with a `$` are tRPC helpers built on TanStack Query. Each one maps to a single TanStack Query call: see [TanStack Query equivalents](https://trpc.io/docs/client/tanstack-react-query/handles#tanstack-equivalents).
> [!NOTE] Info
> We have prefixed the handle APIs with `unstable_` as they're new, but you're safe to use them! [Read more](https://trpc.io/docs/faq#unstable).
See [Prefetching with handles](https://trpc.io/docs/client/tanstack-react-query/handles).
### `queryKey` - getting the query key and performing operations on the query client
Available for all query procedures. Allows you to access the query key in a type-safe manner.
```ts no-check
const queryKey = trpc.path.to.query.queryKey();
```
Since Tanstack React Query uses fuzzy matching for query keys, you can also create a partial query key for any sub-path to match all queries belonging to a router:
```ts no-check
const queryKey = trpc.router.pathKey();
```
Or even the root path to match all tRPC queries:
```ts no-check
const queryKey = trpc.pathKey();
```
### `infiniteQueryKey` - getting the infinite query key
Available for all query procedures that take a cursor input. Allows you to access the query key for an infinite query in a type-safe manner.
```ts no-check
const infiniteQueryKey = trpc.path.to.query.infiniteQueryKey({
/** input */
});
```
The result can be used with query client methods like `getQueryData`, `setQueryData`, `invalidateQueries`, etc.
```ts no-check
// 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` - creating query filters
Available for all query procedures. Allows creating [query filters](https://tanstack.com/query/latest/docs/framework/react/guides/filters#query-filters) in a type-safe manner.
```ts no-check
const queryFilter = trpc.path.to.query.queryFilter(
{
/** input */
},
{
// Any Tanstack React Query filter
predicate: (query) => {
return !!query.state.data;
},
},
);
```
Like with query keys, if you want to run a filter across a whole router you can use `pathFilter` to target any sub-path.
```ts no-check
const queryFilter = trpc.path.pathFilter({
// Any Tanstack React Query filter
predicate: (query) => {
return !!query.state.data;
},
});
```
Useful for creating filters that can be passed to client methods like `queryClient.invalidateQueries` etc.
### `infiniteQueryFilter` - creating infinite query filters
Available for all query procedures that take a cursor input. Allows creating [query filters](https://tanstack.com/query/latest/docs/framework/react/guides/filters#query-filters) for infinite queries in a type-safe manner.
```ts no-check
const infiniteQueryFilter = trpc.path.to.query.infiniteQueryFilter(
{
/** input */
},
{
// Any Tanstack React Query filter
predicate: (query) => {
return !!query.state.data;
},
},
);
```
Useful for creating filters that can be passed to client methods like `queryClient.invalidateQueries` etc.
```ts no-check
await queryClient.invalidateQueries(
trpc.path.to.query.infiniteQueryFilter(
{},
{
predicate: (query) => {
// Filter logic based on query state
return query.state.status === 'success';
},
},
),
);
```
### `mutationOptions` - creating mutation options
Available for all mutation procedures. Provides a type-safe identity function for constructing options that can be passed to `useMutation`.
```ts no-check
const mutationOptions = trpc.path.to.mutation.mutationOptions({
// Any Tanstack React Query options
onSuccess: (data) => {
// do something with the data
},
});
```
### `mutationKey` - getting the mutation key
Available for all mutation procedures. Allows you to get the mutation key in a type-safe manner.
```ts no-check
const mutationKey = trpc.path.to.mutation.mutationKey();
```
### `subscriptionOptions` - creating subscription options
TanStack does not provide a subscription hook, so we continue to expose our own abstraction here which works with a [standard tRPC subscription setup](https://trpc.io/docs/server/subscriptions).
Available for all subscription procedures. Provides a type-safe identity function for constructing options that can be passed to `useSubscription`.
Note that you need to have either the [`httpSubscriptionLink`](https://trpc.io/docs/client/links/httpSubscriptionLink) or [`wsLink`](https://trpc.io/docs/client/links/wsLink) configured in your tRPC client to use subscriptions.
```tsx no-check
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
},
onComplete: () => {
// the subscription was ended by the server, `status` is now `'idle'`
},
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 <>{/* ... */}</>;
}
```
### Query Key Prefixing
When using multiple tRPC providers in a single application (e.g., connecting to different backend services), queries with the same path will collide in the cache. You can prevent this by enabling query key prefixing.
```tsx no-check
// Without prefixes - these would collide!
const authQuery = useQuery(trpcAuth.list.queryOptions()); // auth service
const billingQuery = useQuery(trpcBilling.list.queryOptions()); // billing service
```
Enable the feature flag when creating your context:
```tsx no-check title="utils/trpc.ts"
// [...]
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: [
/* ... */
],
});
```
```tsx no-check title="App.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>
);
}
```
```tsx no-check title="components/MyComponent.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>
);
}
```
The query keys will be properly prefixed to avoid collisions:
```tsx no-check
// Example of how the query keys look with prefixes
const queryKeys = [
[['billing'], ['list'], { type: 'query' }],
[['account'], ['list'], { type: 'query' }],
];
```
### Inferring Input and Output types
When you need to infer the input and output types for a procedure or router, there are 2 options available depending on the situation.
Infer the input and output types of a full router
```ts no-check
import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server';
import type { AppRouter } from './server/router';
export type Inputs = inferRouterInputs<AppRouter>;
export type Outputs = inferRouterOutputs<AppRouter>;
```
Infer types for a single procedure
```ts no-check
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>;
}
```
### Accessing the tRPC client
If you used the [setup with React Context](https://trpc.io/docs/client/tanstack-react-query/setup#3a-set-up-the-trpc-context-provider), you can access the tRPC client using the `useTRPCClient` hook.
```tsx no-check
import { useTRPCClient } from './trpc';
async function Component() {
const trpcClient = useTRPCClient();
const result = await trpcClient.getUser.query({
id: '1',
});
}
```
If you [setup without React Context](https://trpc.io/docs/client/tanstack-react-query/setup#3c-set-up-without-react-context),
you can import the global client instance directly instead.
```ts no-check
import { client } from './trpc';
const result = await client.path.to.procedure.query({
/** input */
id: 'foo',
});
```