@orpc/react-query is a thin wrapper around @orpc/tanstack-query that re-exports everything with React-specific types from @tanstack/react-query.
Installation
npm install @orpc/react-query @tanstack/react-query
Setup
Create your oRPC client
import { createORPCClient } from '@orpc/client'
import { RPCLink } from '@orpc/client/fetch'
import type { RouterClient } from '@orpc/server'
import type { router } from './server'
const link = new RPCLink({ url: '/rpc' })
export const orpc: RouterClient<typeof router> = createORPCClient(link)
Create React Query utils
import { createORPCReactQueryUtils } from '@orpc/react-query'
import { orpc } from './client'
export const orpcUtils = createORPCReactQueryUtils(orpc)
Add the QueryClientProvider
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
const queryClient = new QueryClient()
export function App() {
return (
<QueryClientProvider client={queryClient}>
<YourApp />
</QueryClientProvider>
)
}
useQuery
import { useQuery } from '@tanstack/react-query'
import { orpcUtils } from './orpc'
export function PlanetList() {
const { data, isLoading, error } = useQuery(
orpcUtils.planet.list.queryOptions({ input: { limit: 10 } }),
)
if (isLoading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return (
<ul>
{data?.map(planet => (
<li key={planet.id}>{planet.name}</li>
))}
</ul>
)
}
useSuspenseQuery
import { useSuspenseQuery } from '@tanstack/react-query'
export function PlanetDetail({ id }: { id: number }) {
const { data } = useSuspenseQuery(
orpcUtils.planet.find.queryOptions({ input: { id } }),
)
return <div>{data.name}</div>
}
useInfiniteQuery
import { useInfiniteQuery } from '@tanstack/react-query'
export function InfinitePlanetList() {
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery(
orpcUtils.planet.list.infiniteOptions({
input: pageParam => ({ cursor: pageParam, limit: 10 }),
initialPageParam: 0,
getNextPageParam: lastPage => lastPage.nextCursor,
}),
)
return (
<div>
{data?.pages.flatMap(page => page).map(planet => (
<div key={planet.id}>{planet.name}</div>
))}
<button onClick={() => fetchNextPage()} disabled={!hasNextPage}>
Load more
</button>
</div>
)
}
useMutation
import { useMutation, useQueryClient } from '@tanstack/react-query'
export function CreatePlanetForm() {
const queryClient = useQueryClient()
const { mutate, isPending } = useMutation(
orpcUtils.planet.create.mutationOptions({
onSuccess: async () => {
await queryClient.invalidateQueries({
queryKey: orpcUtils.planet.list.key({ type: 'query' }),
})
},
}),
)
return (
<button
disabled={isPending}
onClick={() => mutate({ name: 'Mars', description: 'The red planet' })}
>
Create planet
</button>
)
}
Prefetching on the server
oRPC clients work on both client and server, so you can prefetch queries in server components or loaders:
import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'
import { orpc } from './server-client' // direct server-side client
import { orpcUtils } from './orpc'
export async function PlanetsPage() {
const queryClient = new QueryClient()
await queryClient.prefetchQuery(
orpcUtils.planet.list.queryOptions({ input: { limit: 10 } }),
)
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<PlanetList />
</HydrationBoundary>
)
}
For server-side usage, create a separate oRPC client that calls your handlers directly rather than going through HTTP. Use createRouterClient(router) from @orpc/server to build a direct server-side client.
Cache invalidation
Use the key() method from GeneralUtils to generate partial matching keys for invalidation:
const queryClient = useQueryClient()
// Specific query with input filter
await queryClient.invalidateQueries({
queryKey: orpcUtils.planet.list.key({ type: 'query', input: { limit: 10 } }),
})
// All queries for a procedure (all inputs)
await queryClient.invalidateQueries({
queryKey: orpcUtils.planet.list.key({ type: 'query' }),
})
// All planet queries (all procedures under planet)
await queryClient.invalidateQueries({
queryKey: orpcUtils.planet.key(),
})