diff --git a/docs/framework/react/reference/useMutation.md b/docs/framework/react/reference/useMutation.md index 6c4469d4fe..231382db2b 100644 --- a/docs/framework/react/reference/useMutation.md +++ b/docs/framework/react/reference/useMutation.md @@ -45,10 +45,11 @@ mutate(variables, { **Options** -- `mutationFn: (variables: TVariables) => Promise` +- `mutationFn: (variables: TVariables, context: MutationFunctionContext) => Promise` - **Required, but only if no default mutation function has been defined** - A function that performs an asynchronous task and returns a promise. - `variables` is an object that `mutate` will pass to your `mutationFn` + - `context` is an object containing the `mutationKey` and the mutation's `meta` - `gcTime: number | Infinity` - The time in milliseconds that unused/inactive cache data remains in memory. When a mutation's cache becomes unused or inactive, that cache data will be garbage collected after this duration. When different cache times are specified, the longest one will be used. - If set to `Infinity`, will disable garbage collection diff --git a/packages/query-core/src/__tests__/mutations.test.tsx b/packages/query-core/src/__tests__/mutations.test.tsx index acf52658c9..1ad0cb4e66 100644 --- a/packages/query-core/src/__tests__/mutations.test.tsx +++ b/packages/query-core/src/__tests__/mutations.test.tsx @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' import { waitFor } from '@testing-library/react' import { MutationObserver } from '../mutationObserver' import { createQueryClient, executeMutation, queryKey, sleep } from './utils' -import type { QueryClient } from '..' +import type { MutationFunctionContext, QueryClient } from '..' import type { MutationState } from '../mutation' describe('mutations', () => { @@ -49,7 +49,30 @@ describe('mutations', () => { ) expect(fn).toHaveBeenCalledTimes(1) - expect(fn).toHaveBeenCalledWith('vars') + expect(fn).toHaveBeenCalledWith('vars', expect.anything()) + }) + + test('should provide context to mutationFn', async () => { + const mutationKey = queryKey() + const vars = 'vars' as const + const meta = { a: 1 } + const mutationFn = vi + .fn<[typeof vars, MutationFunctionContext], Promise<'data'>>() + .mockResolvedValue('data') + + const mutation = new MutationObserver(queryClient, { + mutationKey, + mutationFn, + meta, + }) + + await mutation.mutate(vars) + + expect(mutationFn).toHaveBeenCalledTimes(1) + const context = mutationFn.mock.calls[0]![1] + expect(context).toBeDefined() + expect(context.mutationKey).toEqual(mutationKey) + expect(context.meta).toEqual(meta) }) test('mutation should set correct success states', async () => { diff --git a/packages/query-core/src/mutation.ts b/packages/query-core/src/mutation.ts index e2e2afd600..fc2c2c97b5 100644 --- a/packages/query-core/src/mutation.ts +++ b/packages/query-core/src/mutation.ts @@ -3,6 +3,7 @@ import { Removable } from './removable' import { createRetryer } from './retryer' import type { DefaultError, + MutationFunctionContext, MutationMeta, MutationOptions, MutationStatus, @@ -167,7 +168,11 @@ export class Mutation< if (!this.options.mutationFn) { return Promise.reject(new Error('No mutationFn found')) } - return this.options.mutationFn(variables) + const mutationFnContext: MutationFunctionContext = { + mutationKey: this.options.mutationKey, + meta: this.meta, + } + return this.options.mutationFn(variables, mutationFnContext) }, onFail: (failureCount, error) => { this.#dispatch({ type: 'failed', failureCount, error }) diff --git a/packages/query-core/src/types.ts b/packages/query-core/src/types.ts index 7b812a061f..76eb2cde01 100644 --- a/packages/query-core/src/types.ts +++ b/packages/query-core/src/types.ts @@ -897,8 +897,14 @@ export type MutationMeta = Register extends { : Record : Record +export type MutationFunctionContext = { + mutationKey?: MutationKey + meta: MutationMeta | undefined +} + export type MutationFunction = ( variables: TVariables, + context: MutationFunctionContext, ) => Promise export interface MutationOptions<