Skip to content

Commit

Permalink
fix(vue-query): improve useMutation reactivity, allow params to be re…
Browse files Browse the repository at this point in the history
…fs (TanStack#4339)

* update

* Update utils.ts

* Update types.ts

* potentail fix to parseArgs typing

* change name of maybeRefArgs

* Update useMutation.ts

* update

* revert some stuff

* Update types.ts

* Update types.ts

* update

* Update types.ts

* Update types.ts

* Update useMutation.ts

* update

* update

* Update useMutation.ts

* Update useMutation.ts

* PR feedback

* Update useMutation.ts

* Update types.ts

* Update useMutation.ts

* Update types.ts

* Update types.ts

* Update useMutation.ts

* Update types.ts

* Update useMutation.ts

* update

* update fn names

* change subscribe to const

* Update types.ts

* Update useMutation.ts

* wrong arg passed in useMutation, unnecessary changes in testing files potentially

* update

* update

* Update useMutation.test.ts

* Update useMutation.test.ts

* update

* Update useMutation.test.ts

* Update useMutation.test.ts
  • Loading branch information
matthewhausman authored Oct 22, 2022
1 parent 30ac34f commit 915d5e9
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 64 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"watch": "concurrently --kill-others \"rollup --config rollup.config.js -w\" \"pnpm run typecheck --watch\"",
"dev": "pnpm run watch",
"prettier": "prettier \"packages/*/{src/**,examples/**/src/**}.{md,js,jsx,ts,tsx,json}\"",
"prettier:write": "pnpm run prettier -- --write",
"prettier:write": "pnpm run prettier --write",
"cipublish": "ts-node scripts/publish.ts"
},
"namespace": "@tanstack",
Expand Down
51 changes: 45 additions & 6 deletions packages/vue-query/src/__tests__/useMutation.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { reactive } from 'vue-demi'
import { reactive, ref } from 'vue-demi'
import { errorMutator, flushPromises, successMutator } from './test-utils'
import { parseMutationArgs, useMutation } from '../useMutation'
import { useQueryClient } from '../useQueryClient'
Expand Down Expand Up @@ -35,11 +35,8 @@ describe('useMutation', () => {

test('should return error when request fails', async () => {
const mutation = useMutation(errorMutator)

mutation.mutate()

mutation.mutate({})
await flushPromises(10)

expect(mutation).toMatchObject({
isIdle: { value: false },
isLoading: { value: false },
Expand Down Expand Up @@ -88,6 +85,48 @@ describe('useMutation', () => {
expect(mutations?.options.mutationKey).toEqual(['bar'])
})

test('should update reactive options deeply', async () => {
type MutationKeyTest = {
entity: string
otherObject: {
name: string
someFn: Function
}
}
const mutationKey = ref<MutationKeyTest[]>([
{
entity: 'test',
otherObject: { name: 'objectName', someFn: () => null },
},
])
const queryClient = useQueryClient()
const mutationCache = queryClient.getMutationCache()
const options = reactive({ mutationKey })
const mutation = useMutation(
(params: string) => successMutator(params),
options,
)

mutationKey.value[0]!.otherObject.name = 'someOtherObjectName'
await flushPromises()
mutation.mutate('xyz')

await flushPromises()

const mutations = mutationCache.getAll()
const relevantMutation = mutations.find((m) => {
return (
Array.isArray(m.options.mutationKey) &&
!!m.options.mutationKey[0].otherObject
)
})

expect(
(relevantMutation?.options.mutationKey as MutationKeyTest[])[0]
?.otherObject.name === 'someOtherObjectName',
)
})

test('should reset state after invoking mutation.reset', async () => {
const mutation = useMutation((params: string) => errorMutator(params))

Expand Down Expand Up @@ -237,7 +276,7 @@ describe('useMutation', () => {
test('should throw on error', async () => {
const mutation = useMutation(errorMutator)

await expect(mutation.mutateAsync()).rejects.toThrowError('Some error')
await expect(mutation.mutateAsync({})).rejects.toThrowError('Some error')

expect(mutation).toMatchObject({
isIdle: { value: false },
Expand Down
17 changes: 8 additions & 9 deletions packages/vue-query/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@ import type { Ref, UnwrapRef } from 'vue-demi'
import type { QueryClient } from './queryClient'

export type MaybeRef<T> = Ref<T> | T
export type MaybeRefDeep<T> = T extends Function
? T
: MaybeRef<
T extends object
? {
[Property in keyof T]: MaybeRefDeep<T[Property]>
}
: T
>

export type MaybeRefDeep<T> = MaybeRef<
T extends object
? {
[Property in keyof T]: MaybeRefDeep<T[Property]>
}
: T
>

export type WithQueryClientKey<T> = T & {
queryClientKey?: string
Expand Down
136 changes: 89 additions & 47 deletions packages/vue-query/src/useMutation.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import { onScopeDispose, reactive, readonly, toRefs, watch } from 'vue-demi'
import {
onScopeDispose,
reactive,
readonly,
toRefs,
watch,
computed,
isRef,
} from 'vue-demi'
import type { ToRefs } from 'vue-demi'
import { MutationObserver } from '@tanstack/query-core'
import type {
MutateOptions,
MutationFunction,
MutationKey,
MutationObserverOptions,
MutateFunction,
MutationObserverResult,
MutationObserverOptions,
} from '@tanstack/query-core'
import { cloneDeepUnref, isQueryKey, updateState } from './utils'
import type { WithQueryClientKey, MaybeRef, MaybeRefDeep } from './types'
import { MutationObserver } from '@tanstack/query-core'
import { cloneDeepUnref, updateState, isMutationKey } from './utils'
import { useQueryClient } from './useQueryClient'
import type { WithQueryClientKey } from './types'

type MutationResult<TData, TError, TVariables, TContext> = Omit<
MutationObserverResult<TData, TError, TVariables, TContext>,
Expand All @@ -23,6 +31,22 @@ export type UseMutationOptions<TData, TError, TVariables, TContext> =
MutationObserverOptions<TData, TError, TVariables, TContext>
>

export type VueMutationObserverOptions<
TData = unknown,
TError = unknown,
TVariables = void,
TContext = unknown,
> = {
[Property in keyof UseMutationOptions<
TData,
TError,
TVariables,
TContext
>]: MaybeRefDeep<
UseMutationOptions<TData, TError, TVariables, TContext>[Property]
>
}

type MutateSyncFunction<
TData = unknown,
TError = unknown,
Expand Down Expand Up @@ -50,18 +74,22 @@ export function useMutation<
TVariables = void,
TContext = unknown,
>(
options: UseMutationOptions<TData, TError, TVariables, TContext>,
options: MaybeRef<
VueMutationObserverOptions<TData, TError, TVariables, TContext>
>,
): UseMutationReturnType<TData, TError, TVariables, TContext>
export function useMutation<
TData = unknown,
TError = unknown,
TVariables = void,
TContext = unknown,
>(
mutationFn: MutationFunction<TData, TVariables>,
options?: Omit<
UseMutationOptions<TData, TError, TVariables, TContext>,
'mutationFn'
mutationFn: MaybeRef<MutationFunction<TData, TVariables>>,
options?: MaybeRef<
Omit<
VueMutationObserverOptions<TData, TError, TVariables, TContext>,
'mutationFn'
>
>,
): UseMutationReturnType<TData, TError, TVariables, TContext>
export function useMutation<
Expand All @@ -70,10 +98,12 @@ export function useMutation<
TVariables = void,
TContext = unknown,
>(
mutationKey: MutationKey,
options?: Omit<
UseMutationOptions<TData, TError, TVariables, TContext>,
'mutationKey'
mutationKey: MaybeRef<MutationKey>,
options?: MaybeRef<
Omit<
VueMutationObserverOptions<TData, TError, TVariables, TContext>,
'mutationKey'
>
>,
): UseMutationReturnType<TData, TError, TVariables, TContext>
export function useMutation<
Expand All @@ -82,11 +112,13 @@ export function useMutation<
TVariables = void,
TContext = unknown,
>(
mutationKey: MutationKey,
mutationFn?: MutationFunction<TData, TVariables>,
options?: Omit<
UseMutationOptions<TData, TError, TVariables, TContext>,
'mutationKey' | 'mutationFn'
mutationKey: MaybeRef<MutationKey>,
mutationFn?: MaybeRef<MutationFunction<TData, TVariables>>,
options?: MaybeRef<
Omit<
VueMutationObserverOptions<TData, TError, TVariables, TContext>,
'mutationKey' | 'mutationFn'
>
>,
): UseMutationReturnType<TData, TError, TVariables, TContext>
export function useMutation<
Expand All @@ -96,20 +128,25 @@ export function useMutation<
TContext = unknown,
>(
arg1:
| MutationKey
| MutationFunction<TData, TVariables>
| UseMutationOptions<TData, TError, TVariables, TContext>,
| MaybeRef<MutationKey>
| MaybeRef<MutationFunction<TData, TVariables>>
| MaybeRef<VueMutationObserverOptions<TData, TError, TVariables, TContext>>,
arg2?:
| MutationFunction<TData, TVariables>
| UseMutationOptions<TData, TError, TVariables, TContext>,
arg3?: UseMutationOptions<TData, TError, TVariables, TContext>,
| MaybeRef<MutationFunction<TData, TVariables>>
| MaybeRef<VueMutationObserverOptions<TData, TError, TVariables, TContext>>,
arg3?: MaybeRef<
VueMutationObserverOptions<TData, TError, TVariables, TContext>
>,
): UseMutationReturnType<TData, TError, TVariables, TContext> {
const options = parseMutationArgs(arg1, arg2, arg3)
const options = computed(() => {
return parseMutationArgs(arg1, arg2, arg3)
})
const queryClient =
options.queryClient ?? useQueryClient(options.queryClientKey)
const defaultedOptions = queryClient.defaultMutationOptions(options)
const observer = new MutationObserver(queryClient, defaultedOptions)

options.value.queryClient ?? useQueryClient(options.value.queryClientKey)
const observer = new MutationObserver(
queryClient,
queryClient.defaultMutationOptions(options.value),
)
const state = reactive(observer.getCurrentResult())

const unsubscribe = observer.subscribe((result) => {
Expand All @@ -126,11 +163,9 @@ export function useMutation<
}

watch(
[() => arg1, () => arg2, () => arg3],
options,
() => {
observer.setOptions(
queryClient.defaultMutationOptions(parseMutationArgs(arg1, arg2, arg3)),
)
observer.setOptions(queryClient.defaultMutationOptions(options.value))
},
{ deep: true },
)
Expand Down Expand Up @@ -158,26 +193,33 @@ export function parseMutationArgs<
TContext = unknown,
>(
arg1:
| MutationKey
| MutationFunction<TData, TVariables>
| UseMutationOptions<TData, TError, TVariables, TContext>,
| MaybeRef<MutationKey>
| MaybeRef<MutationFunction<TData, TVariables>>
| MaybeRef<VueMutationObserverOptions<TData, TError, TVariables, TContext>>,
arg2?:
| MutationFunction<TData, TVariables>
| UseMutationOptions<TData, TError, TVariables, TContext>,
arg3?: UseMutationOptions<TData, TError, TVariables, TContext>,
): UseMutationOptions<TData, TError, TVariables, TContext> {
| MaybeRef<MutationFunction<TData, TVariables>>
| MaybeRef<VueMutationObserverOptions<TData, TError, TVariables, TContext>>,
arg3?: MaybeRef<
VueMutationObserverOptions<TData, TError, TVariables, TContext>
>,
): WithQueryClientKey<
MutationObserverOptions<TData, TError, TVariables, TContext>
> {
let options = arg1

if (isQueryKey(arg1)) {
if (typeof arg2 === 'function') {
options = { ...arg3, mutationKey: arg1, mutationFn: arg2 }
if (isMutationKey(arg1)) {
const plainFn = isRef(arg2) ? arg2.value : arg2
const plainOptions = isRef(arg3) ? arg3.value : arg3
if (typeof plainFn === 'function') {
options = { ...plainOptions, mutationKey: arg1, mutationFn: plainFn }
} else {
options = { ...arg2, mutationKey: arg1 }
}
}

if (typeof arg1 === 'function') {
options = { ...arg2, mutationFn: arg1 }
const plainFn = isRef(arg1) ? arg1.value : arg1
const plainOptions = isRef(arg2) ? arg2.value : arg2
if (typeof plainFn === 'function') {
options = { ...plainOptions, mutationFn: plainFn }
}

return cloneDeepUnref(options) as UseMutationOptions<
Expand Down
7 changes: 6 additions & 1 deletion packages/vue-query/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { QueryKey } from '@tanstack/query-core'
import type { QueryKey, MutationKey } from '@tanstack/query-core'
import { isRef, unref } from 'vue-demi'
import type { UnwrapRef } from 'vue-demi'
import type { MaybeRef } from './types'

export const VUE_QUERY_CLIENT = 'VUE_QUERY_CLIENT'

Expand All @@ -14,6 +15,10 @@ export function isQueryKey(value: unknown): value is QueryKey {
return Array.isArray(value)
}

export function isMutationKey(value: unknown): value is MaybeRef<MutationKey> {
return Array.isArray(isRef(value) ? value.value : value)
}

export function updateState(
state: Record<string, unknown>,
update: Record<string, any>,
Expand Down

0 comments on commit 915d5e9

Please sign in to comment.