Skip to main content
@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

1

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)
2

Create React Query utils

import { createORPCReactQueryUtils } from '@orpc/react-query'
import { orpc } from './client'

export const orpcUtils = createORPCReactQueryUtils(orpc)
3

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(),
})

Build docs developers (and LLMs) love