Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/framework/react/reference/mutationOptions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
id: mutationOptions
title: mutationOptions
---

```tsx
mutationOptions({
mutationFn,
...options,
})
```

**Options**

You can generally pass everything to `mutationOptions` that you can also pass to [`useMutation`](./useMutation.md).
18 changes: 18 additions & 0 deletions docs/framework/react/typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,24 @@ const data = queryClient.getQueryData<Group[]>(['groups'])
[//]: # 'TypingQueryOptions'
[//]: # 'Materials'

## Typing Mutation Options

Similarly to `queryOptions`, you can use `mutationOptions` to extract mutation options into a separate function:

```ts
function useGroupPostMutation() {
const queryClient = useQueryClient()

return mutationOptions({
mutationKey: ['groups'],
mutationFn: executeGroups,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] })
},
})
}
```

## Further Reading

For tips and tricks around type inference, have a look at [React Query and TypeScript](./community/tkdodos-blog.md#6-react-query-and-typescript) from
Expand Down
51 changes: 51 additions & 0 deletions packages/react-query/src/__tests__/mutationOptions.test-d.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { describe, expectTypeOf, it } from 'vitest'
import { dataTagSymbol } from '@tanstack/query-core'
import { mutationOptions } from '../mutationOptions'

describe('mutationOptions', () => {
it('should not allow excess properties', () => {
return mutationOptions({
mutationFn: () => Promise.resolve(5),
mutationKey: ['key'],
// @ts-expect-error this is a good error, because onMutates does not exist!
onMutates: 1000,
})
})

it('should infer types for callbacks', () => {
return mutationOptions({
mutationFn: () => Promise.resolve(5),
mutationKey: ['key'],
onSuccess: (data) => {
expectTypeOf(data).toEqualTypeOf<number>()
},
})
})

it('should tag the mutationKey with the result type of the MutationFn', () => {
const { mutationKey } = mutationOptions({
mutationKey: ['key'],
mutationFn: () => Promise.resolve(5),
})

expectTypeOf(mutationKey[dataTagSymbol]).toEqualTypeOf<number>()
})

it('should tag the mutationKey with unknown if there is no mutationFn', () => {
const { mutationKey } = mutationOptions({
mutationKey: ['key'],
})

expectTypeOf(mutationKey[dataTagSymbol]).toEqualTypeOf<unknown>()
})

it('should tag the mutationKey with the result type of the MutationFn if onSuccess is used', () => {
const { mutationKey } = mutationOptions({
mutationKey: ['key'],
mutationFn: () => Promise.resolve(5),
onSuccess: () => {},
})

expectTypeOf(mutationKey[dataTagSymbol]).toEqualTypeOf<number>()
})
})
14 changes: 14 additions & 0 deletions packages/react-query/src/__tests__/mutationOptions.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { describe, expect, it } from 'vitest'
import { mutationOptions } from '../mutationOptions'
import type { UseMutationOptions } from '../types'

describe('mutationOptions', () => {
it('should return the object received as a parameter without any modification.', () => {
const object: UseMutationOptions = {
mutationKey: ['key'],
mutationFn: () => Promise.resolve(5),
} as const

expect(mutationOptions(object)).toStrictEqual(object)
})
})
105 changes: 105 additions & 0 deletions packages/react-query/src/mutationOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import type {
DataTag,
DefaultError,
InitialDataFunction,
MutationFunction,
OmitKeyof,
SkipToken,
} from '@tanstack/query-core'
import type { UseMutationOptions } from './types'

export type UndefinedInitialDataOptions<
TMutationFnData = unknown,
TError = DefaultError,
TData = void,
TMutationKey = unknown,
> = UseMutationOptions<TMutationFnData, TError, TData, TMutationKey> & {
initialData?:
| undefined
| InitialDataFunction<NonUndefinedGuard<TMutationFnData>>
| NonUndefinedGuard<TMutationFnData>
}

export type UnusedSkipTokenOptions<
TMutationFnData = unknown,
TError = DefaultError,
TData = void,
TMutationKey = unknown,
> = OmitKeyof<
UseMutationOptions<TMutationFnData, TError, TData, TMutationKey>,
'mutationFn'
> & {
mutationFn?: Exclude<
UseMutationOptions<
TMutationFnData,
TError,
TData,
TMutationKey
>['mutationFn'],
SkipToken | undefined
>
}

type NonUndefinedGuard<T> = T extends undefined ? never : T

export type DefinedInitialDataOptions<
TMutationFnData = unknown,
TError = DefaultError,
TData = void,
TMutationKey = unknown,
> = Omit<
UseMutationOptions<TMutationFnData, TError, TData, TMutationKey>,
'mutationFn'
> & {
initialData:
| NonUndefinedGuard<TMutationFnData>
| (() => NonUndefinedGuard<TMutationFnData>)
mutationFn?: MutationFunction<TMutationFnData, TMutationKey>
}

export function mutationOptions<
TMutationFnData = unknown,
TError = DefaultError,
TData = void,
TMutationKey = unknown,
>(
options: DefinedInitialDataOptions<
TMutationFnData,
TError,
TData,
TMutationKey
>,
): DefinedInitialDataOptions<TMutationFnData, TError, TData, TMutationKey> & {
mutationKey: DataTag<TMutationKey, TMutationFnData, TError>
}

export function mutationOptions<
TMutationFnData = unknown,
TError = DefaultError,
TData = void,
TMutationKey = unknown,
>(
options: UnusedSkipTokenOptions<TMutationFnData, TError, TData, TMutationKey>,
): UnusedSkipTokenOptions<TMutationFnData, TError, TData, TMutationKey> & {
mutationKey: DataTag<TMutationKey, TMutationFnData, TError>
}

export function mutationOptions<
TMutationFnData = unknown,
TError = DefaultError,
TData = void,
TMutationKey = unknown,
>(
options: UndefinedInitialDataOptions<
TMutationFnData,
TError,
TData,
TMutationKey
>,
): UndefinedInitialDataOptions<TMutationFnData, TError, TData, TMutationKey> & {
mutationKey: DataTag<TMutationKey, TMutationFnData, TError>
}
Comment on lines +60 to +101
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant Function Overloads

The implementation has three function overloads but the actual implementation at line 103 simply returns the options object unchanged. Since all overloads return the same type structure with just different input types, this creates unnecessary complexity without functional differences in behavior.

Standards
  • Algorithm-Correctness-Implementation
  • Logic-Verification-Simplicity


export function mutationOptions(options: unknown) {
return options
Comment on lines +103 to +104
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent Type Handling

Implementation returns options unmodified but type signatures promise enhanced return with DataTag. Runtime behavior doesn't match type contract, causing potential type inconsistencies.

Suggested change
export function mutationOptions(options: unknown) {
return options
export function mutationOptions(options: unknown) {
return { ...options, mutationKey: options.mutationKey }
Standards
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • DbC-Type-Contract

}
Comment on lines +103 to +105
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementation Duplication Risk

The implementation simply returns the input without modification, which could lead to confusion about its purpose. This function appears to be primarily for type inference rather than runtime behavior, which should be clarified.

Standards
  • Clean-Code-Function-Purpose
  • Clean-Code-Clarity

Comment on lines +103 to +105
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate Implementation Logic

The implementation simply returns the input parameter without modification. This pattern duplicates the implementation style of queryOptions but misses an opportunity for shared abstraction between similar functions, potentially leading to divergent implementations in future maintenance.

Standards
  • Clean-Code-DRY
  • Maintainability-Quality-Abstraction
  • Design-Pattern-Template

Comment on lines +103 to +105
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplify Implementation Logic

The implementation simply returns the input parameter unchanged, which means the function serves only as a type annotation helper. This pattern could be simplified with a generic identity function or type assertion to reduce code complexity while maintaining the same type safety.

Standards
  • Algorithm-Correctness-Implementation
  • Logic-Verification-Simplicity

Loading