oRPC TanStack Query Integration

Seamlessly integrate oRPC with TanStack Query (React, Vue, Solid, Svelte, Angular).

SKILL.md

GitHub

Tanstack Query Integration

TanStack Query is a robust solution for asynchronous state management.

Installation

npm install @orpc/tanstack-query@latest

Setup

import { createTanstackQueryUtils } from '@orpc/tanstack-query'

export const orpc = createTanstackQueryUtils(client)

Avoiding key conflicts:

const userORPC = createTanstackQueryUtils(userClient, { path: ['user'] })
const postORPC = createTanstackQueryUtils(postClient, { path: ['post'] })

Query Options

const query = useQuery(orpc.planet.find.queryOptions({
  input: { id: 123 },
  context: { cache: true },
}))

Streamed Query Options

For Event Iterator — data is an array of events appended as they arrive:

const query = useQuery(orpc.streamed.experimental_streamedOptions({
  input: { id: 123 },
  context: { cache: true },
  queryFnOptions: { refetchMode: 'reset', maxChunks: 3 },
  retry: true,
}))

Live Query Options

For Event Iterator — data is always the latest event:

const query = useQuery(orpc.live.experimental_liveOptions({
  input: { id: 123 },
  context: { cache: true },
  retry: true,
}))

Infinite Query Options

const query = useInfiniteQuery(orpc.planet.list.infiniteOptions({
  input: (pageParam: number | undefined) => ({ limit: 10, offset: pageParam }),
  context: { cache: true },
  initialPageParam: undefined,
  getNextPageParam: lastPage => lastPage.nextPageParam,
}))

Mutation Options

const mutation = useMutation(orpc.planet.create.mutationOptions({
  context: { cache: true },
}))

mutation.mutate({ name: 'Earth' })

Query/Mutation Keys

  • .key: Partial matching for revalidation/invalidation
  • .queryKey: Full matching for Query Options
  • .streamedKey, .infiniteKey, .mutationKey: full matching variants
const queryClient = useQueryClient()

queryClient.invalidateQueries({ queryKey: orpc.planet.key() })
queryClient.invalidateQueries({ queryKey: orpc.planet.key({ type: 'query' }) })
queryClient.invalidateQueries({ queryKey: orpc.planet.find.key({ input: { id: 123 } }) })

queryClient.setQueryData(orpc.planet.find.queryKey({ input: { id: 123 } }), (old) => {
  return { ...old, id: 123, name: 'Earth' }
})

Calling Clients

const planet = await orpc.planet.find.call({ id: 123 })

Reactive Options (Vue/Solid)

// Options as function
const query = useQuery(
  () => orpc.planet.find.queryOptions({ input: { id: id() } })
)

// Computed options
const query = useQuery(computed(
  () => orpc.planet.find.queryOptions({ input: { id: id.value } })
))

Default Options

const orpc = createTanstackQueryUtils(client, {
  experimental_defaults: {
    planet: {
      find: {
        queryOptions: {
          staleTime: 60 * 1000,
          retry: 3,
        },
      },
      create: {
        mutationOptions: {
          onSuccess: (output, input, _, ctx) => {
            ctx.client.invalidateQueries({ queryKey: orpc.planet.key() })
          },
        },
      },
    },
  },
})

Error Handling

import { isDefinedError } from '@orpc/client'

const mutation = useMutation(orpc.planet.create.mutationOptions({
  onError: (error) => {
    if (isDefinedError(error)) {
      // Handle type-safe error
    }
  }
}))

skipToken for Disabling Queries

const query = useQuery(
  orpc.planet.list.queryOptions({
    input: search ? { search } : skipToken,
  })
)

Hydration

To support SSR, extend the serializer:

import { StandardRPCJsonSerializer } from '@orpc/client/standard'

const serializer = new StandardRPCJsonSerializer({
  customJsonSerializers: [],
})

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      queryKeyHashFn(queryKey) {
        const [json, meta] = serializer.serialize(queryKey)
        return JSON.stringify({ json, meta })
      },
      staleTime: 60 * 1000,
    },
    dehydrate: {
      serializeData(data) {
        const [json, meta] = serializer.serialize(data)
        return { json, meta }
      }
    },
    hydrate: {
      deserializeData(data) {
        return serializer.deserialize(data.json, data.meta)
      }
    },
  }
})