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