@orpc/vue-query provides Vue-specific utilities for TanStack Query. It wraps @tanstack/vue-query types and handles Vue’s reactive refs automatically — inputs and context can be plain values or MaybeRefOrGetter.
Installation
npm install @orpc/vue-query @tanstack/vue-query
Setup
Install the Vue Query plugin
import { VueQueryPlugin } from '@tanstack/vue-query'
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.use(VueQueryPlugin)
app.mount('#app')
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 Vue Query utils
import { createORPCVueQueryUtils } from '@orpc/vue-query'
import { orpc } from './client'
export const orpcUtils = createORPCVueQueryUtils(orpc)
useQuery
<script setup lang="ts">
import { useQuery } from '@tanstack/vue-query'
import { orpcUtils } from './orpc'
const { data, isLoading, error } = useQuery(
orpcUtils.planet.list.queryOptions({ input: { limit: 10 } }),
)
</script>
<template>
<div v-if="isLoading">Loading...</div>
<div v-else-if="error">{{ error.message }}</div>
<ul v-else>
<li v-for="planet in data" :key="planet.id">{{ planet.name }}</li>
</ul>
</template>
Because inputs can be MaybeRefOrGetter, you can pass reactive refs directly and the query will re-run when the ref changes:
<script setup lang="ts">
import { ref } from 'vue'
import { useQuery } from '@tanstack/vue-query'
import { orpcUtils } from './orpc'
const limit = ref(10)
const { data } = useQuery(
orpcUtils.planet.list.queryOptions({ input: limit }),
)
// The query re-fetches automatically when `limit` changes
</script>
useInfiniteQuery
<script setup lang="ts">
import { useInfiniteQuery } from '@tanstack/vue-query'
import { orpcUtils } from './orpc'
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery(
orpcUtils.planet.list.infiniteOptions({
input: pageParam => ({ cursor: pageParam, limit: 10 }),
initialPageParam: 0,
getNextPageParam: lastPage => lastPage.nextCursor,
}),
)
</script>
useMutation
<script setup lang="ts">
import { useMutation, useQueryClient } from '@tanstack/vue-query'
import { orpcUtils } from './orpc'
const queryClient = useQueryClient()
const { mutate, isPending } = useMutation(
orpcUtils.planet.create.mutationOptions({
onSuccess: async () => {
await queryClient.invalidateQueries({
queryKey: orpcUtils.planet.list.key({ type: 'query' }),
})
},
}),
)
</script>
<template>
<button :disabled="isPending" @click="mutate({ name: 'Mars' })">
Create planet
</button>
</template>
Cache invalidation
Use the key() method from GeneralUtils to generate partial matching keys:
const queryClient = useQueryClient()
// All planet.list queries (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(),
})
@orpc/vue-query uses @tanstack/vue-query types. The queryOptions() return value includes a reactive queryKey (a ComputedRef) so that TanStack Query re-subscribes when inputs change.