Skip to content

feat(react-query): add mutationOptions #8960

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
596896d
feat(react-query): add mutationOptions
Ubinquitous Apr 6, 2025
ff15e5d
test(react-query): add DataTag test case
Ubinquitous Apr 7, 2025
ea54b58
Merge branch 'main' into feature/react-query-mutation-options
TkDodo May 1, 2025
2972edd
fix(react-query): remove unnecessary types from mutation
Ubinquitous May 1, 2025
08a5026
fix(react-query): remove unncessary type overload
Ubinquitous May 1, 2025
f3b74c0
Merge branch 'main' into feature/react-query-mutation-options
manudeli May 1, 2025
a4560d3
Merge branch 'main' into feature/react-query-mutation-options
manudeli May 5, 2025
6889638
chore(react-query): add mutationOptions to barrel file
Ubinquitous May 5, 2025
b844dee
Merge branch 'main' into feature/react-query-mutation-options
manudeli May 6, 2025
e61227d
Merge branch 'main' into feature/react-query-mutation-options
manudeli May 6, 2025
33d3e9f
fix(react-query): fix test eslint issue
Ubinquitous May 7, 2025
fd7b9f9
docs(react-query): add more examples
Ubinquitous May 7, 2025
6ee8c76
Merge branch 'main' into feature/react-query-mutation-options
manudeli May 7, 2025
299a19f
Merge branch 'main' into feature/react-query-mutation-options
manudeli May 9, 2025
2622902
Merge branch 'main' into feature/react-query-mutation-options
TkDodo May 13, 2025
9ded37d
test(react-query): add more test cases
Ubinquitous May 20, 2025
48d867b
chore(react-query): Change mutaitonKey to required
Ubinquitous Jun 6, 2025
05f4fc0
fix(react-query): fix test code type error
Ubinquitous Jun 6, 2025
b202d6e
test(react-query): add testcase when used with other mutation util
Ubinquitous Jun 7, 2025
167fb8c
fix(react-query): fix error test code and avoid use deprecateed method
Ubinquitous Jun 7, 2025
2b85c72
fix(react-query): fix error test code and avoid use deprecateed method
Ubinquitous Jun 7, 2025
df3545a
fix(react-query): fix import detect error
Ubinquitous Jun 7, 2025
08769ac
fix(react-query): fix import detect error
Ubinquitous Jun 7, 2025
36a8af1
fix(react-query): add function overload
Ubinquitous Jun 10, 2025
22f5ed2
test(react-query): fix mutation options test code
Ubinquitous Jun 10, 2025
1661565
Update docs/framework/react/typescript.md
TkDodo Jun 21, 2025
d7587ba
Merge branch 'main' into feature/react-query-mutation-options
TkDodo Jun 21, 2025
7ee1f5a
Update docs/framework/react/reference/mutationOptions.md
TkDodo Jun 21, 2025
d682a88
Update docs/framework/react/typescript.md
manudeli Jun 21, 2025
f32a7e0
fix: update mutationOptions type definition to allow optional mutatio…
manudeli Jun 22, 2025
0729167
ci: apply automated fixes
autofix-ci[bot] Jun 22, 2025
8730872
Merge branch 'main' into feature/react-query-mutation-options
manudeli Jun 22, 2025
ac2d73f
Merge branch 'main' into feature/react-query-mutation-options
TkDodo Jun 27, 2025
8929c6a
Merge branch 'main' into feature/react-query-mutation-options
manudeli Jun 28, 2025
4adc529
Merge branch 'main' into feature/react-query-mutation-options
manudeli Jun 28, 2025
98e3662
Merge branch 'main' into feature/react-query-mutation-options
manudeli Jun 29, 2025
2f7eb30
chore: add Nick-Lucas as co-author
manudeli Jun 29, 2025
144dcd5
Merge branch 'main' into feature/react-query-mutation-options
manudeli Jul 1, 2025
fe35750
Merge branch 'main' into feature/react-query-mutation-options
manudeli Jul 5, 2025
49f5c39
test(react-query): fix test code and add used with useMutation withou…
Ubinquitous Jul 7, 2025
5b73933
Merge branch 'main' into feature/react-query-mutation-options
TkDodo Jul 9, 2025
bc48f1a
Update packages/react-query/src/__tests__/mutationOptions.test-d.tsx
TkDodo Jul 9, 2025
357269f
docs: add mutationOptions to sidebar config
TkDodo Jul 9, 2025
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'] })
},
})
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

It might be worth highlighting in the documentation that mutationOptions can be reused across different interfaces—such as useMutation, useIsMutating, and queryClient.isMutating.

Suggested change
function useGroupPostMutation() {
const queryClient = useQueryClient()
return mutationOptions({
mutationKey: ['groups'],
mutationFn: executeGroups,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] })
},
})
}
function groupMutationOptions() {
return mutationOptions({
mutationKey: ['groups'],
mutationFn: addGroup,
})
}
useMutation({
...groupMutationOptions()
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['groups'] })
})
useIsMutating(groupMutationOptions())
queryClient.isMutating(groupMutationOptions())

```

## 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
23 changes: 23 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,23 @@
import { describe, expectTypeOf, it } from 'vitest'
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>()
},
})
})
})
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>
}

export function mutationOptions(options: unknown) {
return options
}
Loading