diff --git a/docs/framework/react/reference/useMutation.md b/docs/framework/react/reference/useMutation.md index 9147c072ef..9337673d57 100644 --- a/docs/framework/react/reference/useMutation.md +++ b/docs/framework/react/reference/useMutation.md @@ -48,10 +48,11 @@ mutate(variables, { **Parameter1 (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 that `mutate` will pass to your `mutationFn`. Contains reference to `QueryClient`, `mutationKey` and optional `meta` object. - `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 779daff63e..229d26289e 100644 --- a/packages/query-core/src/__tests__/mutations.test.tsx +++ b/packages/query-core/src/__tests__/mutations.test.tsx @@ -50,8 +50,37 @@ describe('mutations', () => { 'vars', ) + const args = fn.mock.calls[0]! + + expect(fn).toHaveBeenCalledTimes(1) + expect(args[0]).toEqual('vars') + }) + + test('should provide MutationFunctionContext to mutateFn', async () => { + const key = queryKey() + const fn = vi.fn() + const meta = { hello: 'world' } + + await executeMutation( + queryClient, + { + mutationKey: key, + mutationFn: fn, + meta: meta, + }, + 'vars', + ) + + const args = fn.mock.calls[0]! expect(fn).toHaveBeenCalledTimes(1) - expect(fn).toHaveBeenCalledWith('vars') + expect(args).toBeDefined() + + const vars = args[0] + const mutationFnContext = args[1] + expect(vars).toEqual('vars') + expect(mutationFnContext.client).toEqual(queryClient) + expect(mutationFnContext.meta).toEqual(meta) + expect(mutationFnContext.mutationKey).toEqual(key) }) test('mutation should set correct success states', async () => { diff --git a/packages/query-core/src/__tests__/utils.test.tsx b/packages/query-core/src/__tests__/utils.test.tsx index 414b892447..9cee1b4642 100644 --- a/packages/query-core/src/__tests__/utils.test.tsx +++ b/packages/query-core/src/__tests__/utils.test.tsx @@ -423,6 +423,7 @@ describe('core/utils', () => { const filters = { mutationKey: ['key1'] } const queryClient = new QueryClient() const mutation = new Mutation({ + client: queryClient, mutationId: 1, mutationCache: queryClient.getMutationCache(), options: {}, diff --git a/packages/query-core/src/mutation.ts b/packages/query-core/src/mutation.ts index 3aced112e3..1ff371a96d 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, @@ -10,10 +11,12 @@ import type { import type { MutationCache } from './mutationCache' import type { MutationObserver } from './mutationObserver' import type { Retryer } from './retryer' +import type { QueryClient } from './queryClient' // TYPES interface MutationConfig { + client: QueryClient mutationId: number mutationCache: MutationCache options: MutationOptions @@ -88,6 +91,7 @@ export class Mutation< options!: MutationOptions readonly mutationId: number + #client: QueryClient #observers: Array> #mutationCache: MutationCache #retryer?: Retryer @@ -95,6 +99,7 @@ export class Mutation< constructor(config: MutationConfig) { super() + this.#client = config.client this.mutationId = config.mutationId this.#mutationCache = config.mutationCache this.#observers = [] @@ -171,7 +176,14 @@ export class Mutation< if (!this.options.mutationFn) { return Promise.reject(new Error('No mutationFn found')) } - return this.options.mutationFn(variables) + + const mutationFnContext: MutationFunctionContext = { + client: this.#client, + meta: this.options.meta, + mutationKey: this.options.mutationKey, + } + + return this.options.mutationFn(variables, mutationFnContext) }, onFail: (failureCount, error) => { this.#dispatch({ type: 'failed', failureCount, error }) diff --git a/packages/query-core/src/mutationCache.ts b/packages/query-core/src/mutationCache.ts index 6ab95fbfed..9477a4a242 100644 --- a/packages/query-core/src/mutationCache.ts +++ b/packages/query-core/src/mutationCache.ts @@ -99,6 +99,7 @@ export class MutationCache extends Subscribable { state?: MutationState, ): Mutation { const mutation = new Mutation({ + client: client, mutationCache: this, mutationId: ++this.#mutationId, options: client.defaultMutationOptions(options), diff --git a/packages/query-core/src/types.ts b/packages/query-core/src/types.ts index 735b8ea263..d6baf29186 100644 --- a/packages/query-core/src/types.ts +++ b/packages/query-core/src/types.ts @@ -1086,8 +1086,15 @@ export type MutationMeta = Register extends { : Record : Record +export type MutationFunctionContext = { + client: QueryClient + meta: MutationMeta | undefined + mutationKey?: MutationKey +} + export type MutationFunction = ( variables: TVariables, + mutationFnContext: MutationFunctionContext, ) => Promise export interface MutationOptions<