UNPKG

@trpc/tanstack-react-query

Version:

TanStack React Query Integration for tRPC

498 lines (392 loc) • 16.2 kB
# 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 Greeting() { const trpc = useTRPC(); const greetingQuery = useQuery(trpc.greeting.queryOptions({ name: 'Jerry' })); // greetingQuery.data === 'Hello Jerry' return <p>{greetingQuery.data}</p>; } ``` ## 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. The reader shows a loading state until the data streams in: a `Suspense` fallback with `useSuspenseQuery`, or `status: 'pending'` with `useQuery`. - `unstable_$ensure` waits until the data is in the cache, so the reader shows the data on its first render. - Handles carry no data, whichever method made them. If the cache loses the data, for example after `queryClient.resetQueries()`, the reader loads it again, so give it a loading state either way. See [When the cache loses the data](https://trpc.io/docs/client/tanstack-react-query/handles#cache-loses-data). - `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', }); ```