diff --git a/docs/content/1.get-started/3.client.md b/docs/content/1.get-started/3.client.md
index 2af3d27..47d58e2 100644
--- a/docs/content/1.get-started/3.client.md
+++ b/docs/content/1.get-started/3.client.md
@@ -42,11 +42,21 @@ As you can see, we passed `AppRouter` as a type argument of `createTRPCNuxtClien
diff --git a/playground/pages/index.vue b/playground/pages/index.vue
index 8b12476..62f4d21 100644
--- a/playground/pages/index.vue
+++ b/playground/pages/index.vue
@@ -8,20 +8,21 @@ const { data } = useNuxtData(todosKey)
const { data: todos, pending, error, refresh } = await $client.todo.getTodos.useQuery()
+const { mutate } = $client.todo.addTodo.useMutation()
+
const addTodo = async () => {
const title = Math.random().toString(36).slice(2, 7)
+
const newData = {
id: Date.now(),
userId: 69,
title,
completed: false
}
- data.value.push(newData)
- try {
- const x = await $client.todo.addTodo.mutate(newData)
- } catch (e) {
- console.log(e)
- }
+
+ const result = await mutate(newData)
+
+ data.value.push(result)
}
diff --git a/src/client/decorationProxy.ts b/src/client/decorationProxy.ts
index 76e1099..a4483ed 100644
--- a/src/client/decorationProxy.ts
+++ b/src/client/decorationProxy.ts
@@ -2,7 +2,7 @@ import { type inferRouterProxyClient } from '@trpc/client'
import { type AnyRouter } from '@trpc/server'
import { createRecursiveProxy } from '@trpc/server/shared'
// @ts-expect-error: Nuxt auto-imports
-import { getCurrentInstance, onScopeDispose, useAsyncData, unref, isRef } from '#imports'
+import { getCurrentInstance, onScopeDispose, useAsyncData, unref, ref, isRef, toRaw } from '#imports'
import { getQueryKeyInternal } from './getQueryKey'
export function createNuxtProxyDecoration (name: string, client: inferRouterProxyClient) {
@@ -52,6 +52,45 @@ export function createNuxtProxyDecoration (name: stri
lazy: isLazy
})
}
+
+ if (lastArg === 'useMutation') {
+ const { trpc, queryKey: customQueryKey, ...asyncDataOptions } = otherOptions || {} as any
+ // Payload will be set by the `mutate` function and used by `useAsyncData`.
+ const payload = ref(null)
+
+ let controller: AbortController
+
+ if (trpc?.abortOnUnmount) {
+ if (getCurrentInstance()) {
+ onScopeDispose(() => {
+ controller?.abort?.()
+ })
+ }
+ controller = typeof AbortController !== 'undefined' ? new AbortController() : {} as AbortController
+ }
+
+ const queryKey = customQueryKey || getQueryKeyInternal(path, undefined)
+
+ const asyncData = useAsyncData(queryKey, () => (client as any)[path].mutate(payload.value, {
+ signal: controller?.signal,
+ ...trpc
+ }), {
+ ...asyncDataOptions,
+ immediate: false
+ })
+
+ // eslint-disable-next-line no-inner-declarations
+ async function mutate (input: any) {
+ payload.value = input
+ await asyncData.execute()
+ return toRaw(asyncData.data.value)
+ }
+
+ return {
+ mutate,
+ ...asyncData
+ }
+ }
return (client as any)[path][lastArg](...args)
})
diff --git a/src/client/types.ts b/src/client/types.ts
index 8383c66..8b30b37 100644
--- a/src/client/types.ts
+++ b/src/client/types.ts
@@ -19,7 +19,7 @@ import type {
KeysOf,
PickFrom,
} from 'nuxt/dist/app/composables/asyncData'
-import type { Ref } from 'vue'
+import type { Ref, UnwrapRef } from 'vue'
interface TRPCRequestOptions extends _TRPCRequestOptions {
abortOnUnmount?: boolean
@@ -87,6 +87,16 @@ export type DecorateProcedure<
query: Resolver
} : TProcedure extends AnyMutationProcedure ? {
mutate: Resolver
+ useMutation: <
+ ResT = inferTransformedProcedureOutput,
+ DataE = TRPCClientErrorLike,
+ DataT = ResT,
+ PickKeys extends KeysOf = KeysOf,
+ >(
+ opts?: Omit, 'lazy'> & {
+ trpc?: TRPCRequestOptions
+ },
+ ) => AsyncData | null, DataE> & { mutate: (input: inferProcedureInput) => Promise | null, DataE>['data']>> },
} : TProcedure extends AnySubscriptionProcedure ? {
subscribe: SubscriptionResolver
} : never