Skip to content

Conversation

joshuaellis
Copy link

@joshuaellis joshuaellis commented Sep 3, 2025

Description

Kicking off where #9193 left off, i've added the same code modifications to add context to mutationFn, in addition i've changed type references & documentation references to the word context in reference to callbacks such as onError | onSuccess | onSettled to the word onMutateResult to avoid confusion – TContext also becomes TOnMutateResult.

I've not changed the implementation of MutationObserverResult from context to scope because this would be a breaking change to the APIs available as far as I can tell – maybe this should be a follow up for v6 to create alignment across the codebase? Deprecated the context value on the mutation state & added scope to be primarily used.

Finally, I was wondering if we should be passing the context to the aforementioned callbacks but then we do have the scope ability to for instance, you could pass the client along as seen in the changed examples... Adds the context to the mutation callbacks referenced above.

Let me know if i've missed anything or misunderstood some of the agreements in the other PR.

Related Issues

Summary by CodeRabbit

  • New Features

    • Mutation lifecycle enhanced: mutation function and callbacks receive a mutation context; onMutate can return a result that is passed to subsequent callbacks.
    • Cache operations in mutation handlers can use the provided context.client.
    • Devtools now shows mutation "scope" in details.
  • Documentation

    • Guides and examples (Angular, React, Svelte, Vue, Solid) updated to reflect new callback signatures and optimistic-update patterns.

Copy link

coderabbitai bot commented Sep 3, 2025

Walkthrough

Docs, examples, core libraries, and framework bindings update mutation APIs to pass a MutationFunctionContext to mutationFn and lifecycle callbacks, add an explicit onMutateResult value returned from onMutate, replace many TContext type names with TOnMutateResult, and switch cache ops in examples to use context.client.

Changes

Cohort / File(s) Summary
Core: runtime & types
packages/query-core/src/mutation.ts, packages/query-core/src/mutationCache.ts, packages/query-core/src/mutationObserver.ts, packages/query-core/src/types.ts, packages/query-core/src/queryClient.ts, packages/query-core/src/utils.ts
Add MutationFunctionContext and pass it to mutationFn and callbacks; onMutate now accepts (variables, context) and may return an onMutateResult; callback signatures gain onMutateResult and context/options; rename mutation generic from TContextTOnMutateResult; propagate client into Mutation construction and state.
Core: tests
packages/query-core/src/__tests__/*, packages/query-core/src/__tests__/mutationObserver.test.tsx, packages/query-core/src/__tests__/mutations.test.tsx, packages/query-core/src/__tests__/mutationCache.test.tsx, packages/query-core/src/__tests__/hydration.test.tsx, packages/query-core/src/__tests__/utils.test.tsx
Update tests to expect new mutationFn context arg, additional callback parameters (onMutateResult, mutationFnContext/options), include client in Mutation options, and assert scope/onMutateResult usage where applicable.
Language bindings: types & hooks
packages/react-query/src/*, packages/solid-query/src/*, packages/vue-query/src/*, packages/svelte-query/src/*, packages/angular-query-experimental/src/*, packages/svelte-query/src/*
Rename public generic TContextTOnMutateResult across types, hooks, and helper functions; update MutationObserver/Mutation generics; propagate new callback signatures into framework-specific types and APIs.
Examples & docs
examples/react/optimistic-updates-cache/src/pages/index.tsx, docs/framework/**/guides/*.md, docs/framework/**/reference/**/*.md, docs/reference/MutationCache.md
Update guides and examples to use onMutate(variables, context), return/consume onMutateResult, and use context.client for cache operations; update reference docs to reflect new callback signatures and type parameter renames.
Devtools & tooling
packages/query-devtools/src/Devtools.tsx, packages/eslint-plugin-query/src/__tests__/mutation-property-order.rule.test.ts
Devtools: read state.scope/onMutateResult where applicable. Tests/fixtures: rename parameter identifiers and adjust logs to match onMutateResult/context usage.
Angular experimental tests
packages/angular-query-experimental/src/__tests__/mutation-options.test-d.ts, packages/angular-query-experimental/src/inject-mutation.ts, packages/angular-query-experimental/src/mutation-options.ts, packages/angular-query-experimental/src/types.ts
Update generics to TOnMutateResult, adjust injectMutation signatures and types, and add MutationFunctionContext type assertions in tests.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Caller
  participant MO as MutationObserver
  participant M as Mutation
  participant MF as mutationFn
  participant QC as QueryClient

  Caller->>MO: mutate(variables)
  MO->>M: execute(variables)
  M->>M: build MutationFunctionContext { client, meta, mutationKey }
  M->>MO: onMutate(variables, context) → onMutateResult
  note right of MO: examples may call context.client.* for cache ops
  M->>MF: mutationFn(variables, context)
  MF-->>M: result / throw
  alt success
    M->>MO: onSuccess(data, variables, onMutateResult, context)
    MO->>QC: context.client.setQueryData / invalidateQueries
  else error
    M->>MO: onError(error, variables, onMutateResult, context)
    MO->>QC: rollback via onMutateResult using context.client
  end
  M->>MO: onSettled(data?, error?, variables, onMutateResult, context)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Assessment against linked issues

Objective Addressed
Add MutationFunctionContext argument to mutateFn (#9193)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Rename of generic parameter to TOnMutateResult across many packages Broad documentation/type rename beyond the single mutationFn context addition requested in #9193.
Addition of explicit onMutateResult parameter to downstream callbacks Extends lifecycle API surface beyond merely providing a MutationFunctionContext to mutationFn.
Introduction/propagation of scope/state changes in MutationState and related places Expands runtime/state surface beyond the linked issue's objective.
Example/docs refactor to use context.client and reorganize optimistic update flows Documentation and example changes are larger-scale user-facing edits beyond the core feature request.

Pre-merge checks (3 passed, 2 warnings)

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning While the core objective of adding MutationFunctionContext to mutationFn is met, the pull request also includes extensive documentation and type parameter renames across multiple frameworks (Angular, React, Svelte, Vue, etc.) as well as deprecating and replacing the context property with scope in mutation state, which were not specified in issue #9193. These broader API surface changes go beyond the scope of the linked issue and mix implementation with widespread documentation and naming updates. Split the extensive documentation and type-parameter renames into a separate follow-up PR or explicitly augment the linked issue description to cover these additional changes, ensuring each PR remains focused on its defined scope.
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly describes the primary change of the pull request—adding context to the mutation function and its lifecycle callbacks—while clearly indicating the scope (query-core) of the feature. By naming both mutationFn and mutation callbacks, it captures the main developer-facing change without extraneous details. This makes the title concise, specific, and directly aligned with the changeset’s focus.
Linked Issues Check ✅ Passed The pull request fully satisfies the objectives of issue #9193 by introducing a MutationFunctionContext argument to mutationFn, passing a context object containing client, meta, and mutationKey, and updating the signatures of onError, onSuccess, and onSettled to accept this context. It aligns the mutate function signature with QueryFunctionContext and implements the requested naming adjustments to avoid conflicts with existing TContext usage.
Description Check ✅ Passed The current description directly addresses the core modifications—adding context to mutationFn, renaming callback context parameters to onMutateResult, and deprecating context in favor of scope—and references the related issue and prior work. It stays on-topic by outlining the implemented changes and linking to the relevant issue without diverging into unrelated details.

Poem

I twitch my whiskers at the change,
A context hops into range.
onMutateResult, tidy and spry,
helps roll back bugs that try to fly.
With client in paw and docs all neat,
this rabbit hops away, upbeat. 🐇✨

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
docs/framework/react/guides/mutations.md (1)

189-214: Fix usage: mutate is referenced but never captured.

Destructure mutate from useMutation (or use mutation.mutate).

Apply:

-useMutation({
+const { mutate } = useMutation({
   mutationFn: addTodo,
-  onSuccess: (data, variables, scope) => {
+  onSuccess: (data, variables, scope) => {
     // I will fire first
   },
   ...
 })
 
 mutate(todo, {
   onSuccess: (data, variables, scope) => {
     // I will fire second!
   },
   ...
 })
docs/framework/angular/guides/mutations.md (1)

249-251: Bug in snippet: missing this. when calling mutate.

Inside the class, reference the instance field.

Apply:

-    mutation.mutate({ title: 'title' })
+    this.mutation.mutate({ title: 'title' })
docs/framework/angular/guides/optimistic-updates.md (1)

154-163: Fix API usage: injectMutation expects a factory function.

Examples should pass a function that returns options, not a plain object.

Apply:

-injectMutation({
+injectMutation(() => ({
   mutationFn: updateTodo,
   // ...
   onSettled: (newTodo, error, variables, scope) => {
     if (error) {
       // do something
     }
   },
-})
+}))
docs/framework/react/reference/useMutation.md (1)

145-147: Typo: “query” → “mutation”.

The “error” description refers to queries in a mutation hook doc.

- The error object for the query, if an error was encountered.
+ The error object for the mutation, if an error was encountered.
packages/query-core/src/mutationObserver.ts (1)

151-154: Drop non-null assertion when forwarding scope to onSuccess.

Matches the proposed scope: TScope | undefined type and avoids passing an undefined value under a non-optional type.

-        if (action?.type === 'success') {
-          this.#mutateOptions.onSuccess?.(action.data, variables, context!)
+        if (action?.type === 'success') {
+          this.#mutateOptions.onSuccess?.(action.data, variables, context)
           this.#mutateOptions.onSettled?.(action.data, null, variables, context)
         } else if (action?.type === 'error') {
🧹 Nitpick comments (37)
packages/vue-query/src/useMutation.ts (1)

63-74: Consider explicit generics for MutationObserver to strengthen inference.

Being explicit avoids accidental widening if options.value loses type detail.

-  const observer = new MutationObserver(client, options.value)
+  const observer = new MutationObserver<TData, TError, TVariables, TScope>(
+    client,
+    options.value
+  )
packages/solid-query/src/types.ts (1)

143-151: Add a short doc blurb for TScope

A quick JSDoc on TScope will reduce confusion for users migrating from “context”.

Apply:

 export interface SolidMutationOptions<
   TData = unknown,
   TError = DefaultError,
   TVariables = void,
-  TScope = unknown,
+  /**
+   * Arbitrary "scope" value produced by onMutate and threaded to callbacks.
+   * Formerly referred to as "context".
+   */
+  TScope = unknown,
 >
docs/reference/MutationCache.md (2)

31-40: Clarify where “scope” comes from in callback signatures

Explicitly state that “scope” is the value returned by the mutation’s onMutate (not the global one).

Apply:

 - `onError?: (error: unknown, variables: unknown, scope: unknown, mutation: Mutation) => Promise<unknown> | unknown`
+ - `onError?: (error: unknown, variables: unknown, scope: unknown, mutation: Mutation) => Promise<unknown> | unknown`
+   - Note: `scope` is the value returned by the mutation’s own `onMutate` handler.
 ...
 - `onSuccess?: (data: unknown, variables: unknown, scope: unknown, mutation: Mutation) => Promise<unknown> | unknown`
+   - Note: `scope` is the value returned by the mutation’s own `onMutate` handler.
 ...
 - `onSettled?: (data: unknown | undefined, error: unknown | null, variables: unknown, scope: unknown, mutation: Mutation) => Promise<unknown> | unknown`
+   - Note: `scope` is the value returned by the mutation’s own `onMutate` handler.

53-54: Add a migration hint (context → scope) for readers

A one-liner helps users upgrading existing code.

Apply:

 - `onMutate` does not allow returning a scope value.
+ `onMutate` does not allow returning a scope value.
+ 
+ Note: In prior versions, this value was referred to as “context” in docs and examples.
packages/query-core/src/__tests__/mutationCache.test.tsx (1)

190-191: Nice: global onMutate args asserted

Consider an extra test proving that a return value from the global onMutate (even if provided) is ignored in favor of the mutation-level onMutate result—guards against regressions.

packages/query-core/src/__tests__/mutations.test.tsx (2)

635-636: Consider adding a test that asserts mutationFn receives the new context.

Nice note about “onMutate can return scope”. To fully exercise the feature, add a small test that asserts mutationFn’s 2nd arg exposes client/meta/mutationKey.


763-785: Rename to scope is good; drop stray console.log in the test.

The onSettled signature update is correct. Remove the debug log to keep tests quiet.

-      console.log(results)
packages/query-core/src/utils.ts (1)

45-60: Generic rename to TScope in MutationFilters is correct. Consider making matchMutation generic too.

Typing matchMutation generically improves inference for predicate.

Additional change outside the selected lines (for matchMutation signature):

-export function matchMutation(
-  filters: MutationFilters,
-  mutation: Mutation<any, any>,
-): boolean {
+export function matchMutation<
+  TData = unknown,
+  TError = DefaultError,
+  TVariables = unknown,
+  TScope = unknown,
+>(
+  filters: MutationFilters<TData, TError, TVariables, TScope>,
+  mutation: Mutation<TData, TError, TVariables, TScope>,
+): boolean {
packages/react-query/src/useMutation.ts (1)

23-27: Doc hint: clarify “scope” vs legacy “context”.

Given MutationObserverResult may still surface a field named context to avoid a breaking change, consider a short JSDoc note on TScope explaining the transitional naming to preempt confusion.

docs/framework/svelte/reference/type-aliases/createmutationresult.md (1)

6-6: Cross-link MutationFunctionContext for discoverability.

Add a “See also” link to the new MutationFunctionContext so readers find the new context passed to mutationFn.

docs/framework/svelte/reference/type-aliases/createmutatefunction.md (1)

6-10: Show args type inline for clarity.

Make the code block explicit about the args type to avoid readers missing the Parameters section.

-```ts
-type CreateMutateFunction<TData, TError, TVariables, TScope> = (...args) => void
-```
+```ts
+type CreateMutateFunction<TData, TError, TVariables, TScope> =
+  (...args: Parameters<MutateFunction<TData, TError, TVariables, TScope>>) => void
+```
docs/framework/svelte/reference/type-aliases/createmutationoptions.md (1)

25-25: Add brief “scope” usage note.

A one-liner describing TScope as the value returned from onMutate and received by success/error/settled callbacks would help readers connect concepts.

docs/framework/angular/reference/functions/mutationoptions.md (1)

9-12: Consider adding a short TScope example.

Add a snippet showing onMutate returning a scope and onSuccess receiving it to make the TScope flow tangible in Angular.

docs/framework/svelte/reference/type-aliases/createbasemutationresult.md (1)

9-13: Clarify TScope vs. MutationObserverResult type-param naming to avoid confusion

MutationObserverResult in v5 still documents its 4th generic as TContext. Using TScope here is fine (it’s just a type argument), but readers may be confused when cross-referencing. Consider adding a short note that “TScope maps 1:1 to the MutationObserverResult fourth generic (named TContext in v5 docs).”

Proposed doc tweak:

 type CreateBaseMutationResult<TData, TError, TVariables, TScope> = Override<
-  MutationObserverResult<TData, TError, TVariables, TScope>,
+  MutationObserverResult<TData, TError, TVariables, TScope>,
   {
     mutate: CreateMutateFunction<TData, TError, TVariables, TScope>
   }
 > & object
+
+> Note: In v5, MutationObserverResult’s 4th type parameter is named `TContext`. Here, `TScope` maps 1:1 to that parameter.
packages/svelte-query/src/createMutation.ts (1)

19-41: Tie options subscription to store lifecycle and simplify mutate initialization

Avoid a dangling options subscription and redundant late init of mutate. Subscribe/unsubscribe within the readable’s start/stop, and initialize mutate once as a stable closure.

Apply:

   const observer = new MutationObserver<TData, TError, TVariables, TScope>(
     client,
     get(optionsStore),
   )
-  let mutate: CreateMutateFunction<TData, TError, TVariables, TScope>
-
-  optionsStore.subscribe(($options) => {
-    mutate = (variables, mutateOptions) => {
-      observer.mutate(variables, mutateOptions).catch(noop)
-    }
-    observer.setOptions($options)
-  })
+  const mutate: CreateMutateFunction<TData, TError, TVariables, TScope> = (
+    variables,
+    mutateOptions,
+  ) => {
+    observer.mutate(variables, mutateOptions).catch(noop)
+  }
 
-  const result = readable(observer.getCurrentResult(), (set) => {
-    return observer.subscribe(notifyManager.batchCalls((val) => set(val)))
-  })
+  const result = readable(observer.getCurrentResult(), (set) => {
+    const unsubscribeObserver = observer.subscribe(
+      notifyManager.batchCalls((val) => set(val)),
+    )
+    const unsubscribeOptions = optionsStore.subscribe(($options) => {
+      observer.setOptions($options)
+    })
+    return () => {
+      unsubscribeOptions()
+      unsubscribeObserver()
+    }
+  })
docs/framework/angular/reference/interfaces/createmutationoptions.md (1)

6-6: Add a brief note about the TContext → TScope rename for Angular docs

For users upgrading or cross-reading older pages, explicitly noting the rename reduces friction.

Proposed addition:

 # Interface: CreateMutationOptions<TData, TError, TVariables, TScope>
+
+> Note: In v5 docs, some references still use `TContext` as the 4th generic. In this page, `TScope` is used and maps 1:1 to that parameter.

Also applies to: 10-10, 20-20

docs/framework/angular/reference/type-aliases/createmutationresult.md (1)

6-11: CreateMutationResult: TScope threading reads correctly.

BaseMutationNarrowing and TState now use TScope consistently.

TVariables defaults to unknown here but void in other Angular docs (e.g., injectMutation). Consider aligning defaults for consistency.

Ensure BaseMutationNarrowing’s generic order is TData, TError, TVariables, TScope in the referenced page.

Also applies to: 22-25

examples/react/optimistic-updates-cache/src/pages/index.tsx (3)

56-58: Type previousTodos to avoid implicit any/unknown.

Apply:

-      const previousTodos = context.client.getQueryData(
-        todoListOptions.queryKey,
-      )
+      const previousTodos = context.client.getQueryData<Todos>(
+        todoListOptions.queryKey,
+      )

49-54: Strengthen scope typing and use queryOptions for invalidation.

Add an explicit Scope type and annotate onMutate’s return, onError/onSettled params; use todoListOptions for symmetry.

Apply:

-    onMutate: async (newTodo: string, context: MutationFunctionContext) => {
+    onMutate: async (newTodo: string, context: MutationFunctionContext): Promise<Scope> => {
@@
-      return { previousTodos, client: context.client }
+      return { previousTodos, client: context.client }
     },
@@
-    onError: (err, variables, scope) => {
+    onError: (err, variables, scope: Scope) => {
       if (scope?.previousTodos) {
         scope.client.setQueryData<Todos>(['todos'], scope.previousTodos)
       }
     },
@@
-    onSettled: (data, error, variables, scope) =>
-      scope?.client.invalidateQueries({ queryKey: ['todos'] }),
+    onSettled: (data, error, variables, scope: Scope) =>
+      scope?.client.invalidateQueries(todoListOptions),

And add near the top (outside changed lines):

type Scope = { previousTodos?: Todos; client: QueryClient }

Also applies to: 71-83


49-54: Remove unused useQueryClient hook and variable.

Since you now use context.client, drop useQueryClient and the queryClient variable to satisfy lint rules.

Outside changed lines, update:

// Remove from import list:
//   useQueryClient,

// Remove:
// const queryClient = useQueryClient()
packages/angular-query-experimental/src/inject-mutation.ts (1)

81-88: Minor: stable mutate wrapper identity (optional).

The arrow wrapper for mutate is recreated when the computed re-evaluates. If consumers rely on referential equality, consider memoizing the wrapper per observer instance. Not a blocker.

docs/framework/react/guides/mutations.md (3)

146-161: Clarify “context” vs “scope” roles in callbacks.

Consider a short callout: context (second arg to onMutate) is the system-provided MutationFunctionContext; scope is the object you return from onMutate and is passed to onSuccess/onError/onSettled. This avoids confusion with the “scope” option shown later.


297-309: Make optimistic update helpers resilient to undefined cache state.

old can be undefined. Use nullish coalescing to avoid runtime errors in examples.

Apply:

-    context.client.setQueryData(['todos'], (old) => [...old, optimisticTodo])
+    context.client.setQueryData(['todos'], (old) => [...(old ?? []), optimisticTodo])
...
-    scope.client.setQueryData(['todos'], (old) =>
-      old.map((todo) => (todo.id === scope.optimisticTodo.id ? result : todo)),
-    )
+    scope.client.setQueryData(['todos'], (old = []) =>
+      old.map((todo) => (todo.id === scope.optimisticTodo.id ? result : todo)),
+    )
...
-    scope.client.setQueryData(['todos'], (old) =>
-      old.filter((todo) => todo.id !== scope.optimisticTodo.id),
-    )
+    scope.client.setQueryData(['todos'], (old = []) =>
+      old.filter((todo) => todo.id !== scope.optimisticTodo.id),
+    )

Also applies to: 311-321


391-402: Terminology collision: “scope” option vs “scope” callback arg.

Since both are named “scope” but serve different purposes (queueing vs payload), add a one-liner note to distinguish them to readers.

docs/framework/angular/guides/mutations.md (2)

93-110: Angular docs: add a brief note distinguishing context vs scope.

Same suggestion as React docs to reduce ambiguity for readers.


214-241: Harden optimistic update examples against undefined cache state.

Align with resilient patterns so examples won’t throw if the todos query hasn’t run yet.

Apply:

-    await context.client.cancelQueries({ queryKey: ['todos'] })
+    await context.client.cancelQueries({ queryKey: ['todos'] })
...
-    context.client.setQueryData(['todos'], (old) => [...old, optimisticTodo])
+    context.client.setQueryData(['todos'], (old) => [...(old ?? []), optimisticTodo])
...
-    scope.client.setQueryData(['todos'], (old) =>
-      old.map((todo) => (todo.id === scope.optimisticTodo.id ? result : todo)),
-    )
+    scope.client.setQueryData(['todos'], (old = []) =>
+      old.map((todo) => (todo.id === scope.optimisticTodo.id ? result : todo)),
+    )
...
-    scope.client.setQueryData(['todos'], (old) =>
-      old.filter((todo) => todo.id !== scope.optimisticTodo.id),
-    )
+    scope.client.setQueryData(['todos'], (old = []) =>
+      old.filter((todo) => todo.id !== scope.optimisticTodo.id),
+    )
docs/framework/angular/guides/optimistic-updates.md (1)

90-103: Make setQueryData calls safe when cache is empty.

Use nullish coalescing/defaults to avoid spreading/iterating undefined.

Apply:

-    await context.client.cancelQueries({ queryKey: ['todos'] })
+    await context.client.cancelQueries({ queryKey: ['todos'] })
-    const previousTodos = context.client.getQueryData(['todos'])
+    const previousTodos = context.client.getQueryData(['todos'])
-    context.client.setQueryData(['todos'], (old) => [...old, newTodo])
+    context.client.setQueryData(['todos'], (old) => [...(old ?? []), newTodo])
...
-    scope.client.invalidateQueries({ queryKey: ['todos'] })
+    scope.client.invalidateQueries({ queryKey: ['todos'] })

Also applies to: 110-113, 125-138, 140-147

packages/angular-query-experimental/src/mutation-options.ts (1)

52-59: Overload 2 matches the first; minor style nit on Omit vs OmitKeyof.

Elsewhere in angular-query-experimental you use OmitKeyof; consider using it here for consistency, but it’s not functionally required.

-  options: Omit<
-    CreateMutationOptions<TData, TError, TVariables, TScope>,
-    'mutationKey'
-  >,
-): Omit<CreateMutationOptions<TData, TError, TVariables, TScope>, 'mutationKey'>
+  options: OmitKeyof<
+    CreateMutationOptions<TData, TError, TVariables, TScope>,
+    'mutationKey'
+  >,
+): OmitKeyof<CreateMutationOptions<TData, TError, TVariables, TScope>, 'mutationKey'>
docs/framework/react/guides/optimistic-updates.md (2)

111-112: Handle undefined cache data in setQueryData updater.

old can be undefined; spreading will throw. Use a null-safe pattern.

-    context.client.setQueryData(['todos'], (old) => [...old, newTodo])
+    context.client.setQueryData(['todos'], (old) => [ ...(old ?? []), newTodo ])

100-101: Use a more accurate function name in “adding a new todo”.

The example says “adding a new todo” but uses updateTodo. Rename to addTodo for clarity.

-useMutation({
-  mutationFn: updateTodo,
+useMutation({
+  mutationFn: addTodo,
docs/framework/react/reference/useMutation.md (2)

93-97: Clarify default scope semantics.

Code treats “unscoped” mutations as fully concurrent; it doesn’t generate a default unique scope id. Reword to avoid implying an auto-id.

- Optional
- Defaults to a unique id (so that all mutations run in parallel)
- Mutations with the same scope id will run in serial
+ Optional
+ Unscoped by default (mutations run in parallel).
+ Mutations with the same `scope.id` will run in serial.

51-56: Minor wording nits.

Tighten phrasing per the new context param and grammar.

- 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.
+ A function that performs an asynchronous task and returns a promise.
+ `variables` is the value that `mutate` passes to your `mutationFn`.
+ `context` is passed to your `mutationFn` and contains `{ client: QueryClient, mutationKey?, meta? }`.
packages/query-core/src/mutationCache.ts (2)

195-208: Type the filters generically to drop casts.

Widen find’s filters to MutationFilters<TData, TError, TVariables, TScope> so the return cast isn’t needed.

-  find<
+  find<
     TData = unknown,
     TError = DefaultError,
     TVariables = any,
-    TScope = unknown,
-  >(
-    filters: MutationFilters,
-  ): Mutation<TData, TError, TVariables, TScope> | undefined {
+    TScope = unknown,
+  >(filters: MutationFilters<TData, TError, TVariables, TScope>):
+    Mutation<TData, TError, TVariables, TScope> | undefined {
     const defaultedFilters = { exact: true, ...filters }
 
     return this.getAll().find((mutation) =>
       matchMutation(defaultedFilters, mutation),
-    ) as Mutation<TData, TError, TVariables, TScope> | undefined
+    ) as Mutation<TData, TError, TVariables, TScope> | undefined
   }

146-149: Consider notifying only on actual removals.

You currently notify “removed” even when nothing was deleted. If listeners rely on semantics, this may be confusing.

packages/query-core/src/mutation.ts (1)

231-236: Optional: expose MutationFunctionContext to cache-level callbacks.

If useful for plugins/integrations, consider adding the context object to MutationCacheConfig handlers for symmetry. Non-breaking since it would be an extra param.

Also applies to: 241-247, 256-261, 270-276

packages/query-core/src/types.ts (1)

1100-1103: Consider making the new context arg optional to reduce friction.

This keeps existing user-annotated MutationFunction types (single-arg) source-compatible without relying on bivariance settings.

-export type MutationFunction<TData = unknown, TVariables = unknown> = (
+export type MutationFunction<TData = unknown, TVariables = unknown> = (
   variables: TVariables,
-  context: MutationFunctionContext,
+  context?: MutationFunctionContext,
 ) => Promise<TData>

If you prefer keeping it required, please confirm repo-wide TS compatibility under strictFunctionTypes: true.

packages/query-core/src/mutationObserver.ts (1)

147-147: Optional: invoke mutateOptions callbacks even without listeners.

Per-call callbacks shouldn’t depend on subscription presence. Consider removing hasListeners() from the guard.

-      if (this.#mutateOptions && this.hasListeners()) {
+      if (this.#mutateOptions) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 43049c5 and c85be41.

📒 Files selected for processing (45)
  • docs/framework/angular/guides/mutations.md (4 hunks)
  • docs/framework/angular/guides/optimistic-updates.md (3 hunks)
  • docs/framework/angular/reference/functions/injectmutation.md (3 hunks)
  • docs/framework/angular/reference/functions/mutationoptions.md (2 hunks)
  • docs/framework/angular/reference/interfaces/basemutationnarrowing.md (6 hunks)
  • docs/framework/angular/reference/interfaces/createmutationoptions.md (2 hunks)
  • docs/framework/angular/reference/type-aliases/createbasemutationresult.md (3 hunks)
  • docs/framework/angular/reference/type-aliases/createmutateasyncfunction.md (2 hunks)
  • docs/framework/angular/reference/type-aliases/createmutatefunction.md (2 hunks)
  • docs/framework/angular/reference/type-aliases/createmutationresult.md (2 hunks)
  • docs/framework/react/guides/mutations.md (4 hunks)
  • docs/framework/react/guides/optimistic-updates.md (3 hunks)
  • docs/framework/react/reference/useMutation.md (3 hunks)
  • docs/framework/svelte/reference/functions/createmutation.md (2 hunks)
  • docs/framework/svelte/reference/type-aliases/createbasemutationresult.md (3 hunks)
  • docs/framework/svelte/reference/type-aliases/createmutateasyncfunction.md (2 hunks)
  • docs/framework/svelte/reference/type-aliases/createmutatefunction.md (2 hunks)
  • docs/framework/svelte/reference/type-aliases/createmutationoptions.md (2 hunks)
  • docs/framework/svelte/reference/type-aliases/createmutationresult.md (2 hunks)
  • docs/reference/MutationCache.md (2 hunks)
  • examples/react/optimistic-updates-cache/src/pages/index.tsx (3 hunks)
  • packages/angular-query-experimental/src/inject-mutation.ts (5 hunks)
  • packages/angular-query-experimental/src/mutation-options.ts (2 hunks)
  • packages/angular-query-experimental/src/types.ts (7 hunks)
  • packages/eslint-plugin-query/src/__tests__/mutation-property-order.rule.test.ts (2 hunks)
  • packages/query-core/src/__tests__/mutationCache.test.tsx (7 hunks)
  • packages/query-core/src/__tests__/mutations.test.tsx (4 hunks)
  • packages/query-core/src/__tests__/utils.test.tsx (1 hunks)
  • packages/query-core/src/mutation.ts (9 hunks)
  • packages/query-core/src/mutationCache.ts (4 hunks)
  • packages/query-core/src/mutationObserver.ts (8 hunks)
  • packages/query-core/src/queryClient.ts (1 hunks)
  • packages/query-core/src/types.ts (8 hunks)
  • packages/query-core/src/utils.ts (2 hunks)
  • packages/react-query/src/__tests__/mutationOptions.test-d.tsx (1 hunks)
  • packages/react-query/src/mutationOptions.ts (1 hunks)
  • packages/react-query/src/types.ts (1 hunks)
  • packages/react-query/src/useMutation.ts (2 hunks)
  • packages/solid-query/src/types.ts (1 hunks)
  • packages/solid-query/src/useMutation.ts (1 hunks)
  • packages/svelte-query/src/createMutation.ts (1 hunks)
  • packages/svelte-query/src/types.ts (1 hunks)
  • packages/vue-query/src/mutationCache.ts (1 hunks)
  • packages/vue-query/src/queryClient.ts (1 hunks)
  • packages/vue-query/src/useMutation.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (22)
packages/vue-query/src/queryClient.ts (1)
packages/query-core/src/types.ts (1)
  • MutationObserverOptions (1142-1149)
packages/react-query/src/__tests__/mutationOptions.test-d.tsx (3)
packages/angular-query-experimental/src/mutation-options.ts (1)
  • mutationOptions (90-99)
packages/react-query/src/mutationOptions.ts (1)
  • mutationOptions (29-38)
packages/query-core/src/types.ts (1)
  • DefaultError (47-51)
packages/query-core/src/queryClient.ts (1)
packages/query-core/src/types.ts (2)
  • OmitKeyof (19-29)
  • MutationObserverOptions (1142-1149)
packages/vue-query/src/mutationCache.ts (3)
packages/vue-query/src/types.ts (1)
  • MaybeRefDeep (29-37)
packages/query-core/src/utils.ts (1)
  • MutationFilters (45-67)
packages/query-core/src/mutation.ts (1)
  • Mutation (84-362)
packages/angular-query-experimental/src/inject-mutation.ts (2)
packages/angular-query-experimental/src/types.ts (3)
  • CreateMutationOptions (162-170)
  • CreateMutationResult (301-314)
  • CreateMutateFunction (175-182)
packages/query-core/src/mutationObserver.ts (1)
  • MutationObserver (22-171)
packages/query-core/src/mutationObserver.ts (4)
packages/query-core/src/types.ts (3)
  • MutationObserverResult (1310-1319)
  • MutationObserverOptions (1142-1149)
  • MutateOptions (1151-1169)
packages/query-core/src/subscribable.ts (1)
  • Subscribable (1-30)
packages/query-core/src/queryClient.ts (1)
  • QueryClient (61-648)
packages/query-core/src/mutation.ts (4)
  • Mutation (84-362)
  • action (293-361)
  • Action (74-80)
  • getDefaultState (364-381)
packages/react-query/src/mutationOptions.ts (2)
packages/react-query/src/types.ts (1)
  • UseMutationOptions (192-200)
packages/angular-query-experimental/src/mutation-options.ts (1)
  • mutationOptions (90-99)
packages/angular-query-experimental/src/types.ts (2)
packages/query-core/src/types.ts (5)
  • OmitKeyof (19-29)
  • MutationObserverOptions (1142-1149)
  • MutateFunction (1171-1179)
  • Override (31-35)
  • MutationObserverResult (1310-1319)
packages/svelte-query/src/types.ts (4)
  • CreateMutateFunction (96-103)
  • CreateMutateAsyncFunction (105-110)
  • CreateBaseMutationResult (112-122)
  • CreateMutationResult (125-130)
packages/react-query/src/types.ts (2)
packages/query-core/src/types.ts (6)
  • OmitKeyof (19-29)
  • MutationObserverOptions (1142-1149)
  • DefaultError (47-51)
  • MutateFunction (1171-1179)
  • Override (31-35)
  • MutationObserverResult (1310-1319)
packages/solid-query/src/types.ts (4)
  • UseMutateFunction (160-167)
  • UseMutateAsyncFunction (169-174)
  • UseBaseMutationResult (176-186)
  • UseMutationResult (188-193)
packages/svelte-query/src/createMutation.ts (4)
packages/svelte-query/src/types.ts (4)
  • StoreOrVal (21-21)
  • CreateMutationOptions (86-94)
  • CreateMutationResult (125-130)
  • CreateMutateFunction (96-103)
packages/svelte-query/src/useQueryClient.ts (1)
  • useQueryClient (4-7)
packages/svelte-query/src/utils.ts (1)
  • isSvelteStore (4-8)
packages/query-core/src/mutationObserver.ts (2)
  • MutationObserver (22-171)
  • mutate (111-126)
packages/query-core/src/__tests__/mutations.test.tsx (1)
packages/query-codemods/src/v5/remove-overloads/utils/index.cjs (1)
  • scope (29-31)
packages/solid-query/src/types.ts (1)
packages/query-core/src/types.ts (5)
  • MutationObserverOptions (1142-1149)
  • DefaultError (47-51)
  • MutateFunction (1171-1179)
  • Override (31-35)
  • MutationObserverResult (1310-1319)
packages/react-query/src/useMutation.ts (2)
packages/react-query/src/types.ts (3)
  • UseMutationOptions (192-200)
  • UseMutationResult (228-233)
  • UseMutateFunction (202-209)
packages/query-core/src/mutationObserver.ts (1)
  • MutationObserver (22-171)
packages/angular-query-experimental/src/mutation-options.ts (4)
packages/query-core/src/types.ts (2)
  • WithRequired (443-445)
  • DefaultError (47-51)
packages/angular-query-experimental/src/types.ts (1)
  • CreateMutationOptions (162-170)
packages/svelte-query/src/types.ts (1)
  • CreateMutationOptions (86-94)
packages/react-query/src/mutationOptions.ts (1)
  • mutationOptions (29-38)
packages/svelte-query/src/types.ts (2)
packages/query-core/src/types.ts (6)
  • OmitKeyof (19-29)
  • MutationObserverOptions (1142-1149)
  • DefaultError (47-51)
  • MutateFunction (1171-1179)
  • Override (31-35)
  • MutationObserverResult (1310-1319)
packages/angular-query-experimental/src/types.ts (3)
  • CreateMutateFunction (175-182)
  • CreateMutateAsyncFunction (187-192)
  • CreateBaseMutationResult (197-207)
packages/vue-query/src/useMutation.ts (3)
packages/query-core/src/types.ts (6)
  • DistributiveOmit (14-17)
  • MutationObserverResult (1310-1319)
  • MutationObserverOptions (1142-1149)
  • DefaultError (47-51)
  • MutateFunction (1171-1179)
  • MutateOptions (1151-1169)
packages/vue-query/src/types.ts (2)
  • ShallowOption (58-63)
  • MaybeRefDeep (29-37)
packages/react-query/src/useMutation.ts (1)
  • useMutation (19-66)
packages/query-core/src/types.ts (3)
packages/query-core/src/queryClient.ts (1)
  • QueryClient (61-648)
packages/vue-query/src/queryClient.ts (1)
  • QueryClient (37-477)
packages/query-core/src/mutation.ts (1)
  • MutationState (26-41)
packages/solid-query/src/useMutation.ts (2)
packages/solid-query/src/types.ts (3)
  • UseMutationOptions (153-158)
  • UseMutationResult (188-193)
  • UseMutateFunction (160-167)
packages/query-core/src/mutationObserver.ts (3)
  • MutationObserver (22-171)
  • mutate (111-126)
  • state (128-142)
packages/query-core/src/mutationCache.ts (3)
packages/query-core/src/mutation.ts (2)
  • Mutation (84-362)
  • MutationState (26-41)
packages/query-core/src/types.ts (1)
  • MutationOptions (1105-1140)
packages/query-core/src/utils.ts (2)
  • MutationFilters (45-67)
  • matchMutation (172-199)
examples/react/optimistic-updates-cache/src/pages/index.tsx (2)
packages/query-core/src/types.ts (1)
  • MutationFunctionContext (1094-1098)
examples/svelte/playground/src/lib/stores.ts (1)
  • Todos (25-25)
packages/query-core/src/utils.ts (2)
packages/query-core/src/mutation.ts (1)
  • Mutation (84-362)
packages/query-core/src/index.ts (1)
  • Mutation (44-44)
packages/query-core/src/mutation.ts (2)
packages/query-core/src/mutationCache.ts (1)
  • MutationCache (84-231)
packages/query-core/src/types.ts (3)
  • MutationOptions (1105-1140)
  • DefaultError (47-51)
  • MutationFunctionContext (1094-1098)
🪛 LanguageTool
docs/framework/react/reference/useMutation.md

[grammar] ~53-~53: There might be a mistake here.
Context: ...asynchronous task and returns a promise. - variables is an object that mutate will pass to...

(QB_NEW_EN)


[grammar] ~68-~68: There might be a mistake here.
Context: ...pe | void> | TScope | void` - Optional - This function will fire before the mutat...

(QB_NEW_EN)

🔇 Additional comments (66)
packages/eslint-plugin-query/src/__tests__/mutation-property-order.rule.test.ts (2)

146-149: Rename looks consistent with scope migration.

The updated callback params and log text align with the new “scope” naming. Tests should keep exercising only ordering, not signature specifics, so this is fine.


205-210: Regression sample updated correctly.

Strings and parameter names reflect the new “scope” term and should keep the rule behavior intact.

packages/vue-query/src/mutationCache.ts (1)

11-19: Drop generic parameterization in the Vue wrappersuper.find is declared with a non-generic filters: MutationFilters (defaults to <unknown, DefaultError, unknown, unknown>), so changing the wrapper to accept MutationFilters<TData, TError, TVariables, TScope> will not satisfy the base signature. To preserve strong typing, the core MutationCache.find signature itself must be updated to tie its filters parameter to its own generics.

Likely an incorrect or invalid review comment.

packages/vue-query/src/queryClient.ts (1)

455-465: Generic rename to TScope looks good and matches core types.

This is a type-only rename; runtime behavior is unchanged. No further action.

packages/vue-query/src/useMutation.ts (5)

27-41: Public generics migrated to TScope — OK.

Aliases and options now consistently use TScope. This aligns with core’s MutationObserverOptions and MutateOptions.


51-61: Return type updated to carry TScope through — OK.

mutate, mutateAsync, and reset signatures match core generics.


27-34: Check PR description vs. code regarding MutationObserverResult generic name.

Code uses MutationObserverResult<..., TScope>, but the PR summary says it remains on context to avoid breaking changes. Please confirm which is intended and update the PR description or types accordingly.


95-99: MutateOptions now parameterized by TScope — OK.

Call-through to observer.mutate remains correct with the new typing.


116-118: Casting result refs with TScope — OK.

Keeps the reactive surface aligned with the new generic.

packages/solid-query/src/types.ts (1)

147-151: Approve code changes – TScope rename fully propagated; no lingering TContext references. Generics align with query-core’s MutationObserverOptions/Result and MutateFunction; no runtime impact.

packages/solid-query/src/useMutation.ts (1)

19-24: Solid useMutation generic rename is consistent

TScope is threaded through options, observer, and returned state correctly. No functional changes; mutate vs mutateAsync separation preserved.

Also applies to: 26-33, 39-40

packages/query-core/src/__tests__/mutationCache.test.tsx (2)

30-43: Tests correctly assert scope threading on error path

Returning 'scope' from onMutate and asserting it arrives at global onError/onSettled matches the intended contract.

Also applies to: 47-53


112-125: Tests correctly assert scope threading on success path

The success flow mirrors the error case; expectations look right.

Also applies to: 129-135

packages/react-query/src/__tests__/mutationOptions.test-d.tsx (1)

57-66: Type inference for scope looks good

The dts test now validates scope inference; matches the updated generics.

packages/query-core/src/__tests__/utils.test.tsx (1)

426-430: Verified all new Mutation calls include client. The only direct invocations in utils.test.tsx and mutationCache.ts both pass the client argument.

packages/query-core/src/__tests__/mutations.test.tsx (1)

815-817: onSettled error-path uses scope correctly.

Expectations read well and match the renamed parameter.

packages/query-core/src/queryClient.ts (1)

511-522: Type rename TContext → TScope in setMutationDefaults is consistent.

No runtime impact; matches the broader generic rename.

docs/framework/svelte/reference/type-aliases/createmutateasyncfunction.md (2)

6-11: Docs updated to TScope — looks good.

Alias now maps to MutateFunction<..., TScope> consistently.


21-21: No lingering TContext references found in docs/framework/svelte; resolving sweep.

packages/react-query/src/useMutation.ts (2)

23-27: Approve TScope rename. The generic parameter rename is consistent, non-breaking, and no stray TContext usages remain in packages/react-query/src.


49-51: TScope forwarding and callback signatures verified. onMutate, onSuccess, onError, and onSettled all use the generic TScope (TContext) and the MutationFunctionContext is wired end-to-end.

docs/framework/svelte/reference/type-aliases/createmutationresult.md (2)

6-6: TScope rename is correct and aligned with core.

Header and alias now use TScope; matches the repo-wide shift. LGTM.

Also applies to: 9-10


18-25: Confirm TVariables default for CreateBaseMutationResult
Ensure the CreateBaseMutationResult (or CreateMutationResult) type in packages/svelte-query defines its TVariables default as unknown, matching the docs’ example.

docs/framework/svelte/reference/type-aliases/createmutatefunction.md (1)

20-27: TScope rename and parameter docs look good.

docs/framework/svelte/reference/type-aliases/createmutationoptions.md (1)

6-6: TScope rename matches underlying MutationObserverOptions.

Signature aligns with core. Good.

Also applies to: 9-10

docs/framework/angular/reference/functions/mutationoptions.md (2)

9-12: Angular docs correctly switched to TScope.

Function generics, param, and return types all updated. Looks consistent.

Also applies to: 49-49, 55-55, 61-61


9-12: Sanity-check TError default = Error across Angular package.

rg search in packages/angular-query-experimental returned no matches — verify whether mutationOptions in the Angular codebase intentionally defaults TError = Error or should align with other frameworks' DefaultError; update types/docs if inconsistent.

docs/framework/angular/reference/functions/injectmutation.md (1)

9-13: TScope rename in injectMutation docs — LGTM; verify cross-links.

The rename from TContext to TScope reads consistently across signature, params, and returns.

Please double-check:

  • Linked pages CreateMutationOptions and CreateMutationResult now document TScope consistently.
  • InjectMutationOptions references TScope too.
  • TVariables default (void here) matches other Angular mutation docs to avoid confusion.

Also applies to: 27-27, 33-33, 45-45

docs/framework/angular/reference/type-aliases/createmutateasyncfunction.md (1)

6-6: CreateMutateAsyncFunction: TScope rename looks good.

Type alias and params align with MutateFunction’s TScope.

Confirm MutateFunction<TData, TError, TVariables, TScope> is updated wherever it’s documented/re-exported in Angular docs.

Also applies to: 9-11, 21-21

packages/react-query/src/mutationOptions.ts (1)

8-16: Approve and merge
Confirmed no remaining TContext references in packages/react-query/src—mutationOptions overloads updated to TScope correctly.

examples/react/optimistic-updates-cache/src/pages/index.tsx (1)

12-12: Import path for MutationFunctionContext is correct
MutationFunctionContext is re-exported by @tanstack/react-query via the export * from '@tanstack/query-core' in packages/react-query/src/index.ts, so the import can remain unchanged.

packages/angular-query-experimental/src/inject-mutation.ts (3)

49-56: TScope rename is correctly threaded through the public API.

Generic parameter rename from TContext → TScope is applied consistently in the function signature and options factory. No behavioral changes.


73-83: Observer/result/mutate types consistently use TScope.

Types for MutationObserver, CreateMutateFunction, and MutationObserverResult (in the subscriber signal and final cast) are aligned. Good cohesion with query-core generics.

Also applies to: 101-106, 166-171


59-64: No remaining TContext in Angular sources
Search across the repo shows no TContext references in the Angular packages—only in documentation and React guides.

docs/framework/angular/reference/type-aliases/createbasemutationresult.md (1)

6-16: Docs: TScope rename looks accurate.

Type alias, members, and Type Parameters now use TScope consistently. Link targets remain valid.

Also applies to: 22-24, 34-34

packages/angular-query-experimental/src/mutation-options.ts (3)

38-46: TScope propagation in overload 1 looks correct.

Generic rename is consistent with CreateMutationOptions<TData, TError, TVariables, TScope> and WithRequired<'mutationKey'>. No functional change.


90-99: Implementation signature aligns with overloads.

Identity return preserves generics with TScope. Looks good.


34-47: No TContext occurrences detected
Confirmed via rg -nP '\bTContext\b' packages/angular-query-experimental returning zero matches.

docs/framework/react/guides/optimistic-updates.md (3)

137-150: Same concerns for the single-todo example.

Verify onMutate’s context param support; otherwise mirror the fallback shown above using useQueryClient and pass client via scope.

-  onMutate: async (newTodo, context) => {
-    await context.client.cancelQueries({ queryKey: ['todos', newTodo.id] })
-    const previousTodo = context.client.getQueryData(['todos', newTodo.id])
-    context.client.setQueryData(['todos', newTodo.id], newTodo)
-    return { previousTodo, newTodo, client: context.client }
-  },
+  onMutate: async (newTodo) => {
+    await client.cancelQueries({ queryKey: ['todos', newTodo.id] })
+    const previousTodo = client.getQueryData(['todos', newTodo.id])
+    client.setQueryData(['todos', newTodo.id], newTodo)
+    return { previousTodo, newTodo, client }
+  },

122-124: Scope usage across callbacks is consistent.

onError/onSettled signatures correctly accept scope as the last parameter and use scope.client.

Also applies to: 156-158, 171-176


102-115: Drop this check: onMutate does receive the context parameter. The MutationOptions type in packages/query-core/src/types.ts defines onMutate?(variables, context: MutationFunctionContext) (lines 1113–1115), so the docs example is correct.

Likely an incorrect or invalid review comment.

packages/react-query/src/types.ts (6)

191-201: Rename to TScope in UseMutationOptions is correct and non-breaking.

Type positionally remains 4th generic; mapping to MutationObserverOptions<TData, TError, TVariables, TScope> is consistent with core.


202-210: UseMutateFunction forwards TScope to MutateFunction.

Correctly narrows mutate’s call signature while keeping fire-and-forget return type.


211-217: UseMutateAsyncFunction alias updated to TScope.

Matches MutateFunction from core.


218-227: UseBaseMutationResult now parameterized with TScope.

Override of MutationObserverResult and mutate/mutateAsync members stay in sync.


228-234: UseMutationResult alias follows TScope.

All downstream generics now consistently use TScope.


191-201: Public types no longer include TContext Verified via ripgrep: no TContext references in the codebase (only in docs).

packages/svelte-query/src/types.ts (2)

90-95: TScope propagation across createMutation types looks consistent.

CreateMutationOptions, mutate function types, base/result types all forward TScope into core generics.

Also applies to: 96-104, 105-111, 112-122, 124-131


85-95: No remaining TContext references. A ripgrep search (rg -nP '\bTContext\b' packages/svelte-query) returned zero matches, confirming all TContext types have been removed.

packages/angular-query-experimental/src/types.ts (3)

166-170: TScope rename across Angular mutation option/result types is coherent.

All relevant aliases (CreateMutationOptions, CreateMutate(Function|Async)Function, CreateBaseMutationResult) forward TScope to core generics.

Also applies to: 175-183, 187-193, 197-207


236-249: Type guards updated to TScope are accurate.

this-narrowing for isSuccess/isError/isPending/isIdle now references CreateMutationResult<..., TScope> and status-based extracts correctly.

Also applies to: 253-266, 270-283, 287-295


212-221: TContext references fully removed across frameworks
No instances of TContext remain in packages/angular-query-experimental or any of the React/Solid/Svelte/Vue query packages—rename is complete.

docs/framework/react/reference/useMutation.md (2)

117-126: Confirm optionality consistency for scope in callbacks.

Types declare onSuccess’s scope as required, while onError/onSettled accept scope | undefined. At runtime scope can be undefined if onMutate returns nothing. If this is intentional (historic), leave as-is; otherwise consider making onSuccess’s scope optional in docs to reflect reality.

Also applies to: 121-124


67-71: Fix onMutate signature: add MutationFunctionContext param.

Docs show only (variables) but implementation/types call onMutate(variables, context: MutationFunctionContext). Align the signature and mention the param.

-`onMutate: (variables: TVariables) => Promise<TScope | void> | TScope | void`
+`onMutate: (variables: TVariables, context: MutationFunctionContext) => Promise<TScope | undefined> | TScope | undefined`
   - Optional
-  - This function will fire before the mutation function is fired and is passed the same variables the mutation function would receive
+  - Runs before `mutationFn`. Receives the same `variables` and a `context` object with `{ client, mutationKey, meta }`.

Likely an incorrect or invalid review comment.

docs/framework/angular/reference/interfaces/basemutationnarrowing.md (2)

6-16: TContext → TScope rename looks consistent.

Interface header and type parameter list are updated correctly.


41-41: Verify “Defined in” anchors.

Line numbers in source links may have shifted with the rename. Please regenerate or validate these anchors to avoid broken/misleading links.

Also applies to: 66-66, 91-91, 116-116

packages/query-core/src/mutationCache.ts (2)

13-26: Callback param rename to scope matches runtime usage.

Config callbacks now receive scope (formerly context) and the mutation instance; aligns with mutation.ts calls.

Also applies to: 31-37


96-104: Injecting client into Mutation is the right move.

Passing client to the constructor enables building MutationFunctionContext without global access.

packages/query-core/src/mutation.ts (4)

18-24: Config now carries QueryClient; wiring looks correct.

Adding client to MutationConfig and storing it privately enables building MutationFunctionContext.

Also applies to: 94-96, 99-110


174-179: Good: centralizes MutationFunctionContext creation.

{ client, meta, mutationKey } is exactly what mutationFn and onMutate need.


215-218: Parity with docs: onMutate receives the context.

This matches the intended API and should unblock auth/header scenarios.


238-239: Scope may be undefined at runtime; confirm type intent.

onSuccess is called with this.state.context!. If onMutate returned nothing (or on resume without prior context), this is undefined. Either:

  • make onSuccess’s third param TScope | undefined, or
  • guarantee a default scope is set before success paths.

Also applies to: 249-250, 263-267, 278-283

packages/query-core/src/types.ts (2)

1094-1099: LGTM: MutationFunctionContext shape is clear and minimal.

Including client, meta, and mutationKey aligns with the goals. No concerns.


1142-1149: TScope propagation across public types looks consistent.

The generic rename and wiring through options, result types, and unions are coherent.

Also applies to: 1172-1179, 1185-1186, 1316-1319

packages/query-core/src/mutationObserver.ts (2)

16-31: LGTM: TScope threading and observer plumbing.

Listener/result generics, options handling, and state mapping look correct.

Also applies to: 55-79, 87-91, 129-142


144-170: No additional non-null assertions on context or scope found. Verified across the repo with ripgrep; the only context! occurrence remains in packages/query-core/src/mutationObserver.ts.

Copy link
Collaborator

@TkDodo TkDodo left a comment

Choose a reason for hiding this comment

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

I've not changed the implementation of MutationObserverResult from context to scope because this would be a breaking change

we should probably deprecate context in mutationState and add a scope field and then remove context in v6

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (6)
packages/query-devtools/src/Devtools.tsx (1)

2419-2431: UI copy still says “Context” while reading “scope”.

Consider renaming the header/label to “Scope” (or “Context (deprecated)”) to avoid confusion during the transition.

-        <div class={cx(styles().detailsHeader, 'tsqd-query-details-header')}>
-          Context Details
-        </div>
+        <div class={cx(styles().detailsHeader, 'tsqd-query-details-header')}>
+          Scope Details
+        </div>
...
-          <Explorer
-            label="Context"
+          <Explorer
+            label="Scope"
             defaultExpanded={['Context']}
             value={activeMutation()!.state.scope}
           />
packages/query-core/src/__tests__/mutations.test.tsx (1)

22-36: Add one small test for undefined scope.

Edge-case: onMutate returns undefined; verify onSuccess/onSettled receive undefined scope.

+test('onSuccess receives undefined scope when onMutate returns nothing', async () => {
+  const onSuccess = vi.fn()
+  const onSettled = vi.fn()
+  const mutation = new MutationObserver(queryClient, {
+    mutationFn: (v: string) => Promise.resolve(v),
+    onMutate: () => undefined,
+    onSuccess,
+    onSettled,
+  })
+  mutation.mutate('x')
+  await vi.advanceTimersByTimeAsync(0)
+  expect(onSuccess).toHaveBeenCalledWith('x', 'x', undefined)
+  expect(onSettled).toHaveBeenCalledWith('x', null, 'x', undefined)
+})
packages/query-core/src/types.ts (1)

1094-1099: Minor doc polish for MutationFunctionContext.

Add brief JSDoc for client/meta/mutationKey to improve DX.

 export type MutationFunctionContext = {
-  client: QueryClient
-  meta: MutationMeta | undefined
-  mutationKey?: MutationKey
+  /** The QueryClient that scheduled this mutation. */
+  client: QueryClient
+  /** The merged meta from defaults and options for this mutation. */
+  meta: MutationMeta | undefined
+  /** The mutationKey, if provided. */
+  mutationKey?: MutationKey
 }
packages/query-core/src/mutation.ts (2)

319-331: Avoid resetting submittedAt on the second pending dispatch.

When onMutate returns a scope, we dispatch pending twice and bump submittedAt. Preserve the original timestamp.

-        case 'pending':
-          return {
+        case 'pending': {
+          const submittedAt =
+            state.status === 'pending' ? state.submittedAt : Date.now()
+          return {
             ...state,
             context: action.scope,
             scope: action.scope,
             data: undefined,
             failureCount: 0,
             failureReason: null,
             error: null,
             isPaused: action.isPaused,
             status: 'pending',
             variables: action.variables,
-            submittedAt: Date.now(),
+            submittedAt,
           }
+        }

242-254: Drop non-null assertions on scope in onSuccess
Remove the unnecessary ! from the scope argument—types now permit scope | undefined so the ! is misleading.

--- packages/query-core/src/mutation.ts
@@ 242c242
-      await this.options.onSuccess?.(data, variables, this.state.scope!)
+      await this.options.onSuccess?.(data, variables, this.state.scope)
--- packages/query-core/src/mutationObserver.ts
@@ 152c152
-          this.#mutateOptions.onSuccess?.(action.data, variables, scope!)
+          this.#mutateOptions.onSuccess?.(action.data, variables, scope)
packages/query-core/src/mutationObserver.ts (1)

144-161: Drop unnecessary non-null assertion on scope in onSuccess

MutateOptions.onSuccess accepts TScope | undefined; scope! isn’t needed and is inconsistent with the other callbacks.

Apply:

-        const scope = this.#currentResult.scope
+        const scope = this.#currentResult.scope
@@
-          this.#mutateOptions.onSuccess?.(action.data, variables, scope!)
+          this.#mutateOptions.onSuccess?.(action.data, variables, scope)

Optional (defensive during the transition period): fall back to legacy context if ever unset.

-        const scope = this.#currentResult.scope
+        const scope =
+          (this.#currentResult as any).scope ??
+          (this.#currentResult as any).context
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c85be41 and 2ae2fbe.

📒 Files selected for processing (5)
  • packages/query-core/src/__tests__/mutations.test.tsx (16 hunks)
  • packages/query-core/src/mutation.ts (12 hunks)
  • packages/query-core/src/mutationObserver.ts (8 hunks)
  • packages/query-core/src/types.ts (8 hunks)
  • packages/query-devtools/src/Devtools.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
packages/query-core/src/types.ts (1)
packages/query-core/src/mutation.ts (1)
  • MutationState (26-45)
packages/query-core/src/__tests__/mutations.test.tsx (1)
packages/query-codemods/src/v5/remove-overloads/utils/index.cjs (1)
  • scope (29-31)
packages/query-core/src/mutationObserver.ts (2)
packages/query-core/src/types.ts (3)
  • MutationObserverResult (1314-1323)
  • MutationObserverOptions (1142-1149)
  • MutateOptions (1151-1173)
packages/query-core/src/mutation.ts (4)
  • Mutation (88-367)
  • action (297-366)
  • Action (78-84)
  • getDefaultState (369-387)
packages/query-core/src/mutation.ts (4)
packages/query-core/src/queryClient.ts (1)
  • QueryClient (61-648)
packages/query-core/src/mutationCache.ts (1)
  • MutationCache (84-231)
packages/query-core/src/types.ts (3)
  • MutationOptions (1105-1140)
  • DefaultError (47-51)
  • MutationFunctionContext (1094-1098)
packages/query-core/src/mutationObserver.ts (3)
  • MutationObserver (22-171)
  • action (144-170)
  • state (128-142)
🔇 Additional comments (18)
packages/query-devtools/src/Devtools.tsx (1)

2426-2431: Switched to state.scope — correct and future-proof.

Reading scope aligns with core changes and preserves behavior (context still mirrors scope in state).

packages/query-core/src/__tests__/mutations.test.tsx (5)

65-68: Assertions now include scope alongside context — looks good.

Keeping both fields verifies back-compat and the new source of truth.

Also applies to: 94-111, 115-132, 136-153


175-192: Error-path expectations with scope are consistent.

The state transitions and mirrored context/scope fields are asserted correctly.

Also applies to: 196-213, 217-234, 238-255


280-296: Restore flow with scope — solid coverage.

Resume semantics and final success state with scope are validated.

Also applies to: 310-320, 324-336


645-648: Comment clarifies onMutate may return scope — good guidance.


775-777: Settled callbacks: scoped argument flows are exercised.

Nice coverage for sync/async and ignored return values.

Also applies to: 827-829, 793-797

packages/query-core/src/types.ts (1)

1094-1103: Scope-threaded mutation typing and MutationFunctionContext — well designed.

  • Adds explicit MutationFunctionContext and passes it to mutationFn/onMutate.
  • Threads TScope through options, observer results, and mutate overloads.
  • Makes scope optional in callbacks, matching runtime realities.

Also applies to: 1114-1133, 1142-1150, 1151-1173, 1175-1183, 1185-1194, 1318-1324

packages/query-core/src/mutation.ts (2)

178-183: Passing a first-class context object — spot on.

Constructing mutationFnContext once and reusing for mutationFn/onMutate is clean and efficient.

Also applies to: 190-191, 219-227


321-323: Mirroring context <- scope for back-compat — good.

Keeps DevTools and users relying on context unbroken.

packages/query-core/src/mutationObserver.ts (9)

16-18: Rename to TScope in listener looks good

Type plumbed through MutationObserverListener correctly.


26-31: Generic rename to TScope + options wiring LGTM

Defaulting TScope = unknown matches the new API surface.


33-37: State/result/observer generics aligned

#currentResult, #currentMutation, and #mutateOptions use TScope consistently.


40-41: Constructor signature updated correctly

Constructor accepts MutationObserverOptions<..., TScope>.


56-79: setOptions type changes are consistent

Prev/next option types use TScope; notify-on-change behavior unchanged.


87-91: Observer update action uses TScope

onMutationUpdate signature matches Mutation.#dispatch’s Action<..., TScope>.


93-101: getCurrentResult returns TScope-aware result

Return type is aligned with MutationObserverResult<..., TScope>.


112-126: mutate options now carry scope

Signature matches MutateOptions<..., TScope> and integrates with MutationCache.build.


129-142: MutationObserverResult includes scope; no context field present
Accessing this.#currentResult.scope is type-safe.

@joshuaellis joshuaellis requested a review from TkDodo September 4, 2025 13:33
@joshuaellis joshuaellis force-pushed the feat/mutationfn-context branch from ea2cc38 to 4d34ab6 Compare September 4, 2025 13:36
@joshuaellis
Copy link
Author

Made all the amends @TkDodo so this is ready for another review when you have time.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
docs/framework/angular/guides/mutations.md (1)

249-251: Fix class property access in example

Inside a class method, call the instance property.

-    mutation.mutate({ title: 'title' })
+    this.mutation.mutate({ title: 'title' })
packages/query-core/src/mutationObserver.ts (1)

145-189: Avoid repeated per-call callbacks and release references; also rename local to reduce confusion.

  • Clear #mutateOptions after invoking success/error/settled callbacks to prevent duplicate invocations on subsequent notifications and to drop captured closures.
  • Minor: rename local variable context to fnContext to avoid confusion with deprecated state.context.

Apply:

       if (this.#mutateOptions && this.hasListeners()) {
         const variables = this.#currentResult.variables!
         const scope = this.#currentResult.scope

-        const context = {
+        const fnContext = {
           client: this.#client,
           meta: this.options.meta,
           mutationKey: this.options.mutationKey,
         } satisfies MutationFunctionContext

         if (action?.type === 'success') {
-          this.#mutateOptions.onSuccess?.(
-            action.data,
-            variables,
-            scope,
-            context,
-          )
+          this.#mutateOptions.onSuccess?.(
+            action.data,
+            variables,
+            scope,
+            fnContext,
+          )
           this.#mutateOptions.onSettled?.(
             action.data,
             null,
             variables,
             scope,
-            context,
+            fnContext,
           )
+          this.#mutateOptions = undefined
         } else if (action?.type === 'error') {
-          this.#mutateOptions.onError?.(action.error, variables, scope, context)
+          this.#mutateOptions.onError?.(
+            action.error,
+            variables,
+            scope,
+            fnContext
+          )
           this.#mutateOptions.onSettled?.(
             undefined,
             action.error,
             variables,
             scope,
-            context,
+            fnContext,
           )
+          this.#mutateOptions = undefined
         }
       }
♻️ Duplicate comments (1)
packages/query-core/src/mutation.ts (1)

242-265: Handlers receive (…, scope, context) as specified

Matches updated MutationOptions signatures and prior review requests to expose client to all callbacks.

🧹 Nitpick comments (6)
packages/react-query/src/__tests__/mutationOptions.test-d.tsx (2)

5-10: Assert the shape of MutationFunctionContext (client/meta/mutationKey), not just the alias.

Adding a couple of property-level assertions makes the test more robust against accidental shape regressions.

 import type {
   DefaultError,
-  MutationFunctionContext,
+  MutationFunctionContext,
+  MutationMeta,
+  MutationKey,
   MutationState,
   WithRequired,
 } from '@tanstack/query-core'
   it('should infer context type correctly', () => {
     mutationOptions<number>({
       mutationFn: (_variables, context) => {
         expectTypeOf(context).toEqualTypeOf<MutationFunctionContext>()
+        expectTypeOf(context.client).toEqualTypeOf<QueryClient>()
+        expectTypeOf(context.meta).toEqualTypeOf<MutationMeta | undefined>()
+        expectTypeOf(context.mutationKey).toEqualTypeOf<MutationKey | undefined>()
         return Promise.resolve(5)
       },

Also applies to: 71-76


58-69: Broaden scope inference checks to all lifecycle callbacks.

Verifying scope propagation on onError/onSettled guards against signature drift.

   it('should infer scope type correctly', () => {
     mutationOptions<number, DefaultError, void, { name: string }>({
       mutationFn: () => Promise.resolve(5),
       mutationKey: ['key'],
       onMutate: () => {
         return { name: 'scope' }
       },
       onSuccess: (_data, _variables, scope) => {
         expectTypeOf(scope).toEqualTypeOf<{ name: string } | undefined>()
       },
+      onError: (_e, _v, scope, _context) => {
+        expectTypeOf(scope).toEqualTypeOf<{ name: string } | undefined>()
+      },
+      onSettled: (_d, _e, _v, scope) => {
+        expectTypeOf(scope).toEqualTypeOf<{ name: string } | undefined>()
+      },
     })
   })

Optionally, add a separate test (no generic TScope) to ensure inference from onMutate return:

it('should infer TScope from onMutate return when omitted', () => {
  mutationOptions({
    mutationFn: () => Promise.resolve(5),
    mutationKey: ['key'],
    onMutate: () => ({ name: 'scope' as const }),
    onSuccess: (_d, _v, scope) => {
      expectTypeOf(scope).toEqualTypeOf<{ name: 'scope' } | undefined>()
    },
  })
})
docs/framework/react/guides/optimistic-updates.md (2)

90-92: Fix doc: scope is the 4th argument, not the last

The text still says the value from onMutate is passed as the “last argument” to onError/onSettled. With the new API, it’s the 4th arg (scope); the last arg is now the context.

Apply:

- To do this, `useMutation`'s `onMutate` handler option allows you to return a value that will later be passed to both `onError` and `onSettled` handlers as the last argument.
+ To do this, `useMutation`'s `onMutate` handler option allows you to return a value that will later be passed to both `onError` and `onSettled` as the scope parameter (4th argument).

171-177: Optional: show context in the onSettled example for consistency

Elsewhere on this page you include context; consider adding it here too for uniformity.

- onSettled: async (newTodo, error, variables, scope) => {
+ onSettled: async (newTodo, error, variables, scope, context) => {
docs/framework/angular/guides/optimistic-updates.md (1)

157-163: Optional: include context param for uniformity

Consider adding context to this onSettled signature as shown above for consistency.

- onSettled: (newTodo, error, variables, scope) => {
+ onSettled: (newTodo, error, variables, scope, context) => {
packages/query-core/src/mutation.ts (1)

178-183: Consider freezing the context object before exposing to user code

Prevents accidental mutation of client/meta. Low risk, optional.

-    const mutationFnContext = {
+    const mutationFnContext = Object.freeze({
       client: this.#client,
       meta: this.options.meta,
       mutationKey: this.options.mutationKey,
-    } satisfies MutationFunctionContext
+    }) satisfies MutationFunctionContext
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2ae2fbe and 4d34ab6.

📒 Files selected for processing (9)
  • docs/framework/angular/guides/mutations.md (4 hunks)
  • docs/framework/angular/guides/optimistic-updates.md (3 hunks)
  • docs/framework/react/guides/mutations.md (4 hunks)
  • docs/framework/react/guides/optimistic-updates.md (3 hunks)
  • examples/react/optimistic-updates-cache/src/pages/index.tsx (3 hunks)
  • packages/query-core/src/mutation.ts (12 hunks)
  • packages/query-core/src/mutationObserver.ts (8 hunks)
  • packages/query-core/src/types.ts (8 hunks)
  • packages/react-query/src/__tests__/mutationOptions.test-d.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/query-core/src/types.ts
  • examples/react/optimistic-updates-cache/src/pages/index.tsx
🧰 Additional context used
🧬 Code graph analysis (3)
packages/query-core/src/mutationObserver.ts (2)
packages/query-core/src/types.ts (3)
  • MutationObserverOptions (1145-1152)
  • MutateOptions (1154-1179)
  • MutationFunctionContext (1094-1098)
packages/query-core/src/mutation.ts (4)
  • Mutation (88-380)
  • action (310-379)
  • Action (78-84)
  • getDefaultState (382-400)
packages/react-query/src/__tests__/mutationOptions.test-d.tsx (3)
packages/angular-query-experimental/src/mutation-options.ts (1)
  • mutationOptions (90-99)
packages/react-query/src/mutationOptions.ts (1)
  • mutationOptions (29-38)
packages/query-core/src/types.ts (2)
  • DefaultError (47-51)
  • MutationFunctionContext (1094-1098)
packages/query-core/src/mutation.ts (4)
packages/query-core/src/queryClient.ts (1)
  • QueryClient (61-648)
packages/query-core/src/mutationCache.ts (1)
  • MutationCache (84-231)
packages/query-core/src/types.ts (3)
  • MutationOptions (1105-1143)
  • DefaultError (47-51)
  • MutationFunctionContext (1094-1098)
packages/query-core/src/mutationObserver.ts (3)
  • MutationObserver (23-190)
  • action (145-189)
  • state (129-143)
🔇 Additional comments (29)
docs/framework/react/guides/optimistic-updates.md (3)

102-115: Good: context passed to onMutate; scope returned for later callbacks

The example correctly uses context.client and returns a scope object for rollback.


118-124: Good: onError/onSettled arity reflects new API

Signatures and usage of scope + context look correct.


137-159: Consistent usage: context.client for cache ops; scope carries snapshot

Looks good and aligns with types.

docs/framework/react/guides/mutations.md (5)

146-162: Correct scope-based lifecycle in examples

onMutate returns a scope; subsequent handlers accept scope. Matches the updated API.


192-214: Consistent handler overrides with scope in mutate options

Signatures are correct and mirror observer notifications.


229-243: Consecutive mutations example reflects new scope param

Accurately documents single-fire behavior for per-call callbacks.


297-321: Good: defaults example uses context.client and returns scope

Reads well and demonstrates the recommended pattern for optimistic updates.


395-402: Scope queueing docs look accurate

The new scope.id serialization behavior is described clearly.

docs/framework/angular/guides/optimistic-updates.md (3)

90-103: Good: onMutate receives context; returns scope

Matches the React counterpart and type signatures.


106-113: Good: onError/onSettled use scope + context

Rollback and invalidation patterns are correct.


125-148: Good: single-item optimistic flow mirrors the list example

Use of scope.newTodo.id and previousTodo is consistent.

docs/framework/angular/guides/mutations.md (4)

93-110: Correct: scope lifecycle demonstrated with Angular injectMutation

Handlers accept scope; messaging is clear.


133-142: Consistent handler overrides include scope

Matches global behavior.


164-173: Consecutive mutations: scope signature is correct

Example aligns with observer semantics.


217-243: Good: defaults use context.client; scope carries optimistic item

Accurate and practical.

packages/query-core/src/mutation.ts (7)

18-24: Public config now injects QueryClient — good direction

Passing client via config unblocks building a proper MutationFunctionContext.


32-37: Back-compat: keep state.context while introducing state.scope

Nice deprecation path by mirroring scope into context.


98-115: Observer/client wiring looks correct

Storing #client and defaulting state via getDefaultState is fine.


219-230: Correctly await onMutate, then propagate returned scope to state

Reference equality check avoids redundant dispatches. Looks good.


310-336: Reducer mirrors scope into deprecated context for transition

Ensures existing consumers of state.context don’t break. Good.


382-400: Default state includes both context (deprecated) and scope

Consistent with reducer semantics.


190-191: No changes required: MutationFunction already includes context
The MutationFunction<TData, TVariables> signature in packages/query-core/src/types.ts defines a second context parameter, and options.mutationFn in mutation.ts is invoked with both variables and mutationFnContext.

packages/query-core/src/mutationObserver.ts (7)

9-12: Import of MutationFunctionContext is correct and aligns with callback signatures.


17-19: TScope migration across listener, class, and fields looks consistent.
Covers listener type, class generics/extends, options, currentResult/currentMutation/mutateOptions.

Also applies to: 23-31, 34-38


56-80: Option typing and observerOptionsUpdated notification path remain intact after TScope migration.
No issues spotted.


88-92: Observer update flow intact.
Type alignment on Action and call sequence (#updateResult then #notify) is good.


94-101: Result typing update LGTM.
Return type now parameterized by TScope; consistent with state shape carrying both scope and deprecated context.


112-127: Mutate signature/update path LGTM.
Per-call MutateOptions now parameterized by TScope and correctly forwarded via execute().


129-143: Result assembly preserves back-compat.
Spreading state (which includes both scope and deprecated context) and narrowing to MutationObserverResult is correct.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/framework/angular/guides/mutations.md (1)

245-253: Broken snippet: missing this. when calling mutate

Inside a class method, mutation must be referenced via this.

 class someComponent {
   // Start mutation in some component:
   mutation = injectMutation(() => ({ mutationKey: ['addTodo'] }))
 
   someMethod() {
-    mutation.mutate({ title: 'title' })
+    this.mutation.mutate({ title: 'title' })
   }
 }
♻️ Duplicate comments (2)
docs/framework/react/guides/mutations.md (1)

146-162: Nice: callbacks now include both scope and context

Including context as the final argument to callbacks (alongside scope) aligns with the new API and resolves the earlier request to document context here.

docs/framework/react/reference/useMutation.md (1)

67-72: Inconsistent signature: onMutate is missing the context parameter

Guides show onMutate: (variables, context) => ..., and context is required to access QueryClient inside onMutate. Update the reference to match.

-- `onMutate: (variables: TVariables) => Promise<TScope | void> | TScope | void`
+- `onMutate: (variables: TVariables, context: MutationFunctionContext) => Promise<TScope | void> | TScope | void`
   - Optional
-  - This function will fire before the mutation function is fired and is passed the same variables the mutation function would receive
+  - This function fires before `mutationFn` and receives the same `variables` plus the `context` (with `QueryClient`, `mutationKey`, optional `meta`)
   - Useful to perform optimistic updates to a resource in hopes that the mutation succeeds
   - The value returned from this function will be passed to both the `onError` and `onSettled` functions in the event of a mutation failure and can be useful for rolling back optimistic updates.
🧹 Nitpick comments (3)
packages/angular-query-experimental/src/__tests__/mutation-options.test-d.ts (1)

75-94: Validate context shape, not just the alias

Great check for MutationFunctionContext across all call sites. Consider asserting the known fields to catch regressions to its structure.

Apply this diff to strengthen the assertions:

 it('should infer context type correctly', () => {
   mutationOptions<number>({
     mutationFn: (_variables, context) => {
       expectTypeOf(context).toEqualTypeOf<MutationFunctionContext>()
+      expectTypeOf(context.client).toEqualTypeOf<QueryClient>()
+      expectTypeOf(context.meta).toEqualTypeOf<MutationFunctionContext['meta']>()
+      expectTypeOf(context.mutationKey).toEqualTypeOf<MutationFunctionContext['mutationKey']>()
       return Promise.resolve(5)
     },
     mutationKey: ['key'],
     onMutate: (_variables, context) => {
       expectTypeOf(context).toEqualTypeOf<MutationFunctionContext>()
+      expectTypeOf(context.client).toEqualTypeOf<QueryClient>()
     },
     onSuccess: (_data, _variables, _scope, context) => {
       expectTypeOf(context).toEqualTypeOf<MutationFunctionContext>()
     },
     onError: (_error, _variables, _scope, context) => {
       expectTypeOf(context).toEqualTypeOf<MutationFunctionContext>()
     },
     onSettled: (_data, _error, _variables, _scope, context) => {
       expectTypeOf(context).toEqualTypeOf<MutationFunctionContext>()
     },
   })
 })
docs/framework/angular/guides/mutations.md (1)

245-245: PascalCase class name (style)

Angular classes conventionally use PascalCase.

-class someComponent {
+class SomeComponent {
docs/framework/react/reference/useMutation.md (1)

51-56: Minor wording/punctuation tweak for clarity

Improve readability per grammar hint.

-  - `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.
+  - `variables`: the object that `mutate` passes to your `mutationFn`
+  - `context`: an object passed to your `mutationFn` containing a reference to the `QueryClient`, the `mutationKey`, and an optional `meta` object
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4d34ab6 and d96376b.

📒 Files selected for processing (5)
  • docs/framework/angular/guides/mutations.md (4 hunks)
  • docs/framework/react/guides/mutations.md (4 hunks)
  • docs/framework/react/reference/useMutation.md (3 hunks)
  • packages/angular-query-experimental/src/__tests__/mutation-options.test-d.ts (2 hunks)
  • packages/query-core/src/mutation.ts (12 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/query-core/src/mutation.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/angular-query-experimental/src/__tests__/mutation-options.test-d.ts (2)
packages/angular-query-experimental/src/mutation-options.ts (1)
  • mutationOptions (90-99)
packages/query-core/src/types.ts (2)
  • DefaultError (47-51)
  • MutationFunctionContext (1094-1098)
🪛 LanguageTool
docs/framework/react/reference/useMutation.md

[grammar] ~53-~53: There might be a mistake here.
Context: ...asynchronous task and returns a promise. - variables is an object that mutate will pass to...

(QB_NEW_EN)


[grammar] ~68-~68: There might be a mistake here.
Context: ...pe | void> | TScope | void` - Optional - This function will fire before the mutat...

(QB_NEW_EN)

🔇 Additional comments (2)
packages/angular-query-experimental/src/__tests__/mutation-options.test-d.ts (2)

11-11: Good addition: importing MutationFunctionContext

Importing from core keeps the test source-of-truth aligned with shared types.


62-73: Scope inference looks correct

Asserting TScope | undefined on lifecycle callbacks matches the optional return from onMutate.

@joshuaellis joshuaellis changed the title feat(query-core): add context to mutationfn feat(query-core): add context to mutationfn & mutation callbacks Sep 4, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/query-core/src/__tests__/hydration.test.tsx (1)

868-871: Bug: post-hydration assertion targets the dehydrated payload instead of the hydrated cache

After hydrate(hydrationClient, parsed), the second assertion still inspects dehydrated. It won’t catch regressions in hydration.

Apply:

-    expect(dehydrated.mutations[0]?.scope?.id).toBe('scope')
+    expect(
+      hydrationCache.find({ mutationKey: ['mutation'] })?.scope?.id
+    ).toBe('scope')
🧹 Nitpick comments (17)
packages/solid-query/src/__tests__/useMutation.test.tsx (5)

586-590: Use partial matcher for scope to reduce brittleness.

Future additions to the scope object could break this strict deep-equality check. Match only what matters (client) with expect.objectContaining(...).

-    expect(onMutate).toHaveBeenCalledWith('todo', {
-      client: queryClient,
-      meta: undefined,
-      mutationKey: undefined,
-    })
+    expect(onMutate).toHaveBeenCalledWith(
+      'todo',
+      expect.objectContaining({ client: queryClient })
+    )

1060-1064: Relax scope matching in onSuccessMutate assertion.

Avoid coupling tests to exact scope shape; assert the presence of the client only.

-    expect(onSuccessMutate).toHaveBeenCalledWith('result2', 'todo', undefined, {
-      client: queryClient,
-      meta: undefined,
-      mutationKey: undefined,
-    })
+    expect(onSuccessMutate).toHaveBeenCalledWith(
+      'result2',
+      'todo',
+      undefined,
+      expect.objectContaining({ client: queryClient })
+    )

1071-1075: Relax scope matching in onSettledMutate assertion.

-      {
-        client: queryClient,
-        meta: undefined,
-        mutationKey: undefined,
-      },
+      expect.objectContaining({ client: queryClient }),

1114-1118: Relax scope matching in onError assertion.

-    expect(onError).toHaveBeenCalledWith(error, 'todo', undefined, {
-      client: queryClient,
-      meta: undefined,
-      mutationKey: undefined,
-    })
+    expect(onError).toHaveBeenCalledWith(
+      error,
+      'todo',
+      undefined,
+      expect.objectContaining({ client: queryClient })
+    )

1203-1207: Relax scope matching in onError assertion (settled error path).

-    expect(onError).toHaveBeenCalledWith(mutateFnError, 'todo', undefined, {
-      client: queryClient,
-      meta: undefined,
-      mutationKey: undefined,
-    })
+    expect(onError).toHaveBeenCalledWith(
+      mutateFnError,
+      'todo',
+      undefined,
+      expect.objectContaining({ client: queryClient })
+    )

If helpful, I can add a targeted test ensuring mutationFn receives the new second-arg scope and that mutateAsync overrides also receive scope.

packages/query-core/src/__tests__/hydration.test.tsx (1)

544-549: Harden assertion for new 4th onSuccess parameter to avoid brittle deep equality

The mutation object may gain fields over time. Matching the exact shape makes this test fragile.

Apply:

-    expect(clientOnSuccess).toHaveBeenCalledWith(
-      { id: 2, text: 'text' },
-      { text: 'text' },
-      { optimisticTodo: { id: 1, text: 'text' } },
-      { client: client, meta: undefined, mutationKey: ['addTodo'] },
-    )
+    expect(clientOnSuccess).toHaveBeenCalledWith(
+      { id: 2, text: 'text' },
+      { text: 'text' },
+      { optimisticTodo: { id: 1, text: 'text' } },
+      expect.objectContaining({ client, meta: undefined, mutationKey: ['addTodo'] }),
+    )
packages/query-core/src/__tests__/mutationObserver.test.tsx (4)

334-339: Use partial match for scope in onSettled (success path).

Prevents test breakage if scope structure evolves.

-      undefined,
-      {
-        client: queryClient,
-        meta: undefined,
-        mutationKey: undefined,
-      },
+      undefined,
+      expect.objectContaining({
+        client: queryClient,
+        meta: undefined,
+        mutationKey: undefined,
+      }),

366-370: Use partial match for scope in onError (error path).

Same rationale: keep assertions resilient to additional scope fields.

-    expect(onError).toHaveBeenCalledWith(error, 'error', undefined, {
-      client: queryClient,
-      meta: undefined,
-      mutationKey: undefined,
-    })
+    expect(onError).toHaveBeenCalledWith(
+      error,
+      'error',
+      undefined,
+      expect.objectContaining({
+        client: queryClient,
+        meta: undefined,
+        mutationKey: undefined,
+      }),
+    )

372-382: Use partial match for scope in onSettled (error path).

Aligns all callback assertions and reduces coupling to scope’s exact shape.

-    expect(onSettled).toHaveBeenCalledWith(
-      undefined,
-      error,
-      'error',
-      undefined,
-      {
-        client: queryClient,
-        meta: undefined,
-        mutationKey: undefined,
-      },
-    )
+    expect(onSettled).toHaveBeenCalledWith(
+      undefined,
+      error,
+      'error',
+      undefined,
+      expect.objectContaining({
+        client: queryClient,
+        meta: undefined,
+        mutationKey: undefined,
+      }),
+    )

323-327: Future-proof the assertion by shape-matching the context argument
Use expect.objectContaining for the fourth argument to match only the current fields (client, meta, mutationKey), avoiding brittle tests if new fields (e.g., signal) are added or ordering changes:

-    expect(onSuccess).toHaveBeenCalledWith('SUCCESS', 'success', undefined, {
-      client: queryClient,
-      meta: undefined,
-      mutationKey: undefined,
-    })
+    expect(onSuccess).toHaveBeenCalledWith(
+      'SUCCESS',
+      'success',
+      undefined,
+      expect.objectContaining({
+        client: queryClient,
+        meta: undefined,
+        mutationKey: undefined,
+      }),
+    )

[current MutationFunctionContext fields: client, meta, mutationKey]

packages/react-query/src/__tests__/useMutation.test.tsx (7)

528-532: Use partial matching for scope object to avoid brittle assertions

Future additions to the mutation scope may break strict deep-equality. Prefer expect.objectContaining to assert only relevant fields.

-    expect(onMutate).toHaveBeenCalledWith('todo', {
-      client: queryClient,
-      meta: undefined,
-      mutationKey: undefined,
-    })
+    expect(onMutate).toHaveBeenCalledWith(
+      'todo',
+      expect.objectContaining({
+        client: queryClient,
+        meta: undefined,
+        mutationKey: undefined,
+      }),
+    )

986-991: Make onSuccess assertion resilient to scope shape changes

Use partial matching for the scope object.

-      undefined,
-      {
+      undefined,
+      expect.objectContaining({
         client: queryClient,
         meta: undefined,
         mutationKey: undefined,
-      },
+      }),

997-1002: Same here: prefer partial match for scope

-      undefined,
-      {
+      undefined,
+      expect.objectContaining({
         client: queryClient,
         meta: undefined,
         mutationKey: undefined,
-      },
+      }),

1009-1014: Use partial matching for mutate-level onSuccess scope

-      undefined,
-      {
+      undefined,
+      expect.objectContaining({
         client: queryClient,
         meta: undefined,
         mutationKey: undefined,
-      },
+      }),

1021-1026: Use partial matching for mutate-level onSettled scope

-      undefined,
-      {
+      undefined,
+      expect.objectContaining({
         client: queryClient,
         meta: undefined,
         mutationKey: undefined,
-      },
+      }),

1060-1064: Use partial matching for onError scope

-    expect(onError).toHaveBeenCalledWith(error, 'todo', undefined, {
-      client: queryClient,
-      meta: undefined,
-      mutationKey: undefined,
-    })
+    expect(onError).toHaveBeenCalledWith(
+      error,
+      'todo',
+      undefined,
+      expect.objectContaining({
+        client: queryClient,
+        meta: undefined,
+        mutationKey: undefined,
+      }),
+    )

1141-1145: Use partial matching for onError scope (settled-error path)

-    expect(onError).toHaveBeenCalledWith(mutateFnError, 'todo', undefined, {
-      client: queryClient,
-      meta: undefined,
-      mutationKey: undefined,
-    })
+    expect(onError).toHaveBeenCalledWith(
+      mutateFnError,
+      'todo',
+      undefined,
+      expect.objectContaining({
+        client: queryClient,
+        meta: undefined,
+        mutationKey: undefined,
+      }),
+    )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d96376b and 0bf7733.

📒 Files selected for processing (5)
  • packages/query-core/src/__tests__/hydration.test.tsx (1 hunks)
  • packages/query-core/src/__tests__/mutationObserver.test.tsx (2 hunks)
  • packages/query-core/src/__tests__/mutations.test.tsx (17 hunks)
  • packages/react-query/src/__tests__/useMutation.test.tsx (4 hunks)
  • packages/solid-query/src/__tests__/useMutation.test.tsx (4 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/query-core/src/tests/mutations.test.tsx

@joshuaellis joshuaellis requested a review from TkDodo September 4, 2025 16:21
Copy link

nx-cloud bot commented Sep 4, 2025

View your CI Pipeline Execution ↗ for commit 88b9e83

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 2m 36s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 1m 22s View ↗

☁️ Nx Cloud last updated this comment at 2025-09-05 14:15:59 UTC

Copy link

pkg-pr-new bot commented Sep 4, 2025

More templates

@tanstack/angular-query-devtools-experimental

npm i https://pkg.pr.new/@tanstack/angular-query-devtools-experimental@9615

@tanstack/angular-query-experimental

npm i https://pkg.pr.new/@tanstack/angular-query-experimental@9615

@tanstack/eslint-plugin-query

npm i https://pkg.pr.new/@tanstack/eslint-plugin-query@9615

@tanstack/query-async-storage-persister

npm i https://pkg.pr.new/@tanstack/query-async-storage-persister@9615

@tanstack/query-broadcast-client-experimental

npm i https://pkg.pr.new/@tanstack/query-broadcast-client-experimental@9615

@tanstack/query-core

npm i https://pkg.pr.new/@tanstack/query-core@9615

@tanstack/query-devtools

npm i https://pkg.pr.new/@tanstack/query-devtools@9615

@tanstack/query-persist-client-core

npm i https://pkg.pr.new/@tanstack/query-persist-client-core@9615

@tanstack/query-sync-storage-persister

npm i https://pkg.pr.new/@tanstack/query-sync-storage-persister@9615

@tanstack/react-query

npm i https://pkg.pr.new/@tanstack/react-query@9615

@tanstack/react-query-devtools

npm i https://pkg.pr.new/@tanstack/react-query-devtools@9615

@tanstack/react-query-next-experimental

npm i https://pkg.pr.new/@tanstack/react-query-next-experimental@9615

@tanstack/react-query-persist-client

npm i https://pkg.pr.new/@tanstack/react-query-persist-client@9615

@tanstack/solid-query

npm i https://pkg.pr.new/@tanstack/solid-query@9615

@tanstack/solid-query-devtools

npm i https://pkg.pr.new/@tanstack/solid-query-devtools@9615

@tanstack/solid-query-persist-client

npm i https://pkg.pr.new/@tanstack/solid-query-persist-client@9615

@tanstack/svelte-query

npm i https://pkg.pr.new/@tanstack/svelte-query@9615

@tanstack/svelte-query-devtools

npm i https://pkg.pr.new/@tanstack/svelte-query-devtools@9615

@tanstack/svelte-query-persist-client

npm i https://pkg.pr.new/@tanstack/svelte-query-persist-client@9615

@tanstack/vue-query

npm i https://pkg.pr.new/@tanstack/vue-query@9615

@tanstack/vue-query-devtools

npm i https://pkg.pr.new/@tanstack/vue-query-devtools@9615

commit: 88b9e83

Copy link

codecov bot commented Sep 4, 2025

Codecov Report

❌ Patch coverage is 96.87500% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 61.71%. Comparing base (a0b96db) to head (88b9e83).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##             main    #9615       +/-   ##
===========================================
+ Coverage   45.47%   61.71%   +16.24%     
===========================================
  Files         209      157       -52     
  Lines        8372     6162     -2210     
  Branches     1905     1722      -183     
===========================================
- Hits         3807     3803        -4     
+ Misses       4118     2033     -2085     
+ Partials      447      326      -121     
Components Coverage Δ
@tanstack/angular-query-devtools-experimental ∅ <ø> (∅)
@tanstack/angular-query-experimental 87.00% <100.00%> (ø)
@tanstack/eslint-plugin-query 83.24% <ø> (ø)
@tanstack/query-async-storage-persister 43.85% <ø> (ø)
@tanstack/query-broadcast-client-experimental 24.39% <ø> (ø)
@tanstack/query-codemods ∅ <ø> (∅)
@tanstack/query-core 97.47% <100.00%> (+<0.01%) ⬆️
@tanstack/query-devtools 3.48% <0.00%> (ø)
@tanstack/query-persist-client-core 79.60% <ø> (ø)
@tanstack/query-sync-storage-persister 84.61% <ø> (ø)
@tanstack/query-test-utils ∅ <ø> (∅)
@tanstack/react-query 96.00% <100.00%> (ø)
@tanstack/react-query-devtools 10.00% <ø> (ø)
@tanstack/react-query-next-experimental ∅ <ø> (∅)
@tanstack/react-query-persist-client 100.00% <ø> (ø)
@tanstack/solid-query 78.13% <100.00%> (ø)
@tanstack/solid-query-devtools ∅ <ø> (∅)
@tanstack/solid-query-persist-client 100.00% <ø> (ø)
@tanstack/svelte-query 87.58% <100.00%> (ø)
@tanstack/svelte-query-devtools ∅ <ø> (∅)
@tanstack/svelte-query-persist-client 100.00% <ø> (ø)
@tanstack/vue-query 71.10% <100.00%> (ø)
@tanstack/vue-query-devtools ∅ <ø> (∅)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@joshuaellis
Copy link
Author

@TkDodo this is ready for review, i've looked at the tests and got the CI green 👍🏼

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
docs/framework/react/reference/useMutation.md (1)

72-76: Context param now documented in callbacks — resolves prior concern.

The context: MutationFunctionContext parameter is included here. This addresses the earlier reviewer note.

🧹 Nitpick comments (2)
docs/framework/react/reference/useMutation.md (2)

51-56: Clarify MutationFunctionContext wording and tighten phrasing.

Rephrase the context note for precision and style.

Apply:

-  - `context` is an object that `mutate` will pass to your `mutationFn`. Contains reference to `QueryClient`, `mutationKey` and optional `meta` object.
+  - `context` is an object that `mutate` will pass to your `mutationFn`. It contains a reference to the `QueryClient`, the `mutationKey`, and an optional `meta` object.

76-79: Note possible undefined scope to set expectations.

Call out that scope may be undefined if onMutate returns void.

Apply:

-  - This function will fire if the mutation encounters an error and will be passed the error.
+  - This function will fire if the mutation encounters an error and will be passed the error. Note: `scope` will be `undefined` if `onMutate` returned `void`.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0bf7733 and 88b9e83.

📒 Files selected for processing (2)
  • docs/framework/react/reference/useMutation.md (3 hunks)
  • packages/query-core/src/utils.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/query-core/src/utils.ts
🧰 Additional context used
🪛 LanguageTool
docs/framework/react/reference/useMutation.md

[grammar] ~53-~53: There might be a mistake here.
Context: ...asynchronous task and returns a promise. - variables is an object that mutate will pass to...

(QB_NEW_EN)


[grammar] ~68-~68: There might be a mistake here.
Context: ...pe | void> | TScope | void` - Optional - This function will fire before the mutat...

(QB_NEW_EN)

🔇 Additional comments (4)
docs/framework/react/reference/useMutation.md (4)

80-83: LGTM — signature parity and parameter order are consistent.


117-121: LGTM — mutate options mirror the top-level options (incl. context).


121-124: LGTM — mutate onError includes scope and context as intended.


125-128: LGTM — mutate onSettled signature matches the documented callbacks.

Copy link
Collaborator

@TkDodo TkDodo left a comment

Choose a reason for hiding this comment

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

oh noo, I forgot that we also have a MutationScope already 😭

export type MutationScope = {
id: string
}

It can be passed as an argument to mutations. So, we need to find a different name again 😅

@joshuaellis
Copy link
Author

It can be passed as an argument to mutations. So, we need to find a different name again 😅

We could append localX to something? alternatively it could be state since its scoped to the mutation?

@joshuaellis
Copy link
Author

Or environment maybe? but that's a common paradigm already used in development practice

@TkDodo
Copy link
Collaborator

TkDodo commented Sep 5, 2025

It can be passed as an argument to mutations. So, we need to find a different name again 😅

We could append localX to something? alternatively it could be state since its scoped to the mutation?

we also have a MutationState already. mutateState might work because it comes from “onMutate”, but it could still be confused with the mutate function. environment is too broad imo.

@joshuaellis
Copy link
Author

yeah mutateState could be confusing – would localContext work? its context, but local to the fn?

@TkDodo
Copy link
Collaborator

TkDodo commented Sep 5, 2025

would localContext work?

yeah that’s not bad, I think session might also work. In the end, it’s just a positional argument in the list and the name of a type so it’s not that important, but still, getting this “right” would be nice :)

@joshuaellis
Copy link
Author

joshuaellis commented Sep 5, 2025

Do you think session is still too broad? maybe sessionContext would be more appropriate?

Also i agree, it's important especially because it's in the docs & helps developers learn 😄

@joshuaellis
Copy link
Author

alt. name instance?

@TkDodo
Copy link
Collaborator

TkDodo commented Sep 8, 2025

what do you think about just onMutateResult and TOnMutateResult for the type. I wouldn’t want this as a name for an object property, but for a positional argument, I think it’s fine as people can still name it however they want

@joshuaellis
Copy link
Author

I like it, explicit. Agreed, if it were an object it'd be a mouthful but positionally & for documentation wise its clear. I'll make that change.

@joshuaellis joshuaellis force-pushed the feat/mutationfn-context branch from 88b9e83 to 1c5a820 Compare September 8, 2025 16:02
@joshuaellis
Copy link
Author

Okay that's been done & i've rebased with latest updates – think i've caught everything...

@joshuaellis joshuaellis requested a review from TkDodo September 8, 2025 16:13
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
docs/framework/angular/reference/functions/injectmutation.md (1)

23-23: Fix default TError to DefaultError

Angular uses DefaultError by default; docs currently show Error.

-• **TError** = `Error`
+• **TError** = `DefaultError`
docs/framework/react/guides/mutations.md (1)

254-264: Fix undefined input variable in mutateAsync example.

The snippet references “todo” before defining it. Provide an input object to avoid confusion.

-try {
-  const todo = await mutation.mutateAsync(todo)
+try {
+  const todo = await mutation.mutateAsync({ title: 'Do Laundry' })
   console.log(todo)
 } catch (error) {
docs/framework/angular/guides/mutations.md (1)

251-253: Fix instance method call

Inside the class, call the instance property: this.mutation.mutate(...).

Apply this diff:

-    mutation.mutate({ title: 'title' })
+    this.mutation.mutate({ title: 'title' })
♻️ Duplicate comments (5)
docs/framework/angular/reference/type-aliases/createmutatefunction.md (1)

6-10: Fix untyped rest parameter in alias to mirror MutateFunction

-type CreateMutateFunction<TData, TError, TVariables, TScope> = (...args) => void
+type CreateMutateFunction<TData, TError, TVariables, TScope> = (
+  ...args: Parameters<MutateFunction<TData, TError, TVariables, TScope>>
+) => void

Run to verify the source type (docs are generated):

#!/bin/bash
# Verify CreateMutateFunction is using a typed rest parameter in source
fd -a types.ts packages/angular-query-experimental | while read f; do
  echo "== $f =="
  rg -nP 'type\s+CreateMutateFunction<[^>]+>\s*=\s*\(' -n "$f" -C2
  rg -nP '\.\.\.args:\s*Parameters<MutateFunction<[^>]+>>' -n "$f" -C2
done

Also applies to: 20-27

docs/framework/svelte/reference/functions/createmutation.md (1)

19-19: Fix default TError in Svelte docs to DefaultError

This mirrors prior feedback on related pages.

-• **TError** = `Error`
+• **TError** = `DefaultError`
docs/framework/react/guides/mutations.md (2)

190-214: Fix undefined identifier: mutate is never declared.

Destructure mutate from useMutation in the example so readers don’t hit a ReferenceError.

-useMutation({
+const { mutate } = useMutation({
   mutationFn: addTodo,
-  onSuccess: (data, variables, onMutateResult, context) => {
+  onSuccess: (data, variables, onMutateResult, context) => {
     // I will fire first
   },
-  onError: (error, variables, onMutateResult, context) => {
+  onError: (error, variables, onMutateResult, context) => {
     // I will fire first
   },
-  onSettled: (data, error, variables, onMutateResult, context) => {
+  onSettled: (data, error, variables, onMutateResult, context) => {
     // I will fire first
   },
 })
 
 mutate(todo, {
   onSuccess: (data, variables, onMutateResult, context) => {
     // I will fire second!
   },
   onError: (error, variables, onMutateResult, context) => {
     // I will fire second!
   },
   onSettled: (data, error, variables, onMutateResult, context) => {
     // I will fire second!
   },
 })

227-243: Fix undefined identifier in “Consecutive mutations”.

Same problem: declare mutate via useMutation before calling it.

-useMutation({
+const { mutate } = useMutation({
   mutationFn: addTodo,
   onSuccess: (data, variables, onMutateResult, context) => {
     // Will be called 3 times
   },
 })
 
 const todos = ['Todo 1', 'Todo 2', 'Todo 3']
 todos.forEach((todo) => {
   mutate(todo, {
     onSuccess: (data, variables, onMutateResult, context) => {
       // Will execute only once, for the last mutation (Todo 3),
       // regardless which mutation resolves first
     },
   })
 })
docs/framework/react/reference/useMutation.md (1)

67-71: Add context parameter to onMutate signature and update wording.

onMutate accepts (variables, context), and its return is forwarded to all three callbacks.

-- `onMutate: (variables: TVariables) => Promise<TScope | void> | TScope | void`
+- `onMutate: (variables: TVariables, context: MutationFunctionContext) => Promise<TScope | void> | TScope | void`
   - Optional
   - This function will fire before the mutation function is fired and is passed the same variables the mutation function would receive
   - Useful to perform optimistic updates to a resource in hopes that the mutation succeeds
-  - The value returned from this function will be passed to both the `onError` and `onSettled` functions in the event of a mutation failure and can be useful for rolling back optimistic updates.
+  - The value returned from this function will be passed to `onSuccess`, `onError`, and `onSettled`. This can be used for optimistic updates and rollbacks.
🧹 Nitpick comments (27)
packages/solid-query/src/__tests__/useMutation.test.tsx (2)

586-590: Future-proof callback-context assertions by partially matching the object

Avoid brittle tests that hardcode every field of the context object. Match only what you care about (client), so additional fields won’t break tests.

Apply this diff:

@@
-    expect(onMutate).toHaveBeenCalledTimes(1)
-    expect(onMutate).toHaveBeenCalledWith('todo', {
-      client: queryClient,
-      meta: undefined,
-      mutationKey: undefined,
-    })
+    expect(onMutate).toHaveBeenCalledTimes(1)
+    expect(onMutate).toHaveBeenCalledWith(
+      'todo',
+      expect.objectContaining({ client: queryClient }),
+    )

@@
-    expect(onSuccessMutate).toHaveBeenCalledTimes(1)
-    expect(onSuccessMutate).toHaveBeenCalledWith('result2', 'todo', undefined, {
-      client: queryClient,
-      meta: undefined,
-      mutationKey: undefined,
-    })
+    expect(onSuccessMutate).toHaveBeenCalledTimes(1)
+    expect(onSuccessMutate).toHaveBeenCalledWith(
+      'result2',
+      'todo',
+      undefined,
+      expect.objectContaining({ client: queryClient }),
+    )

@@
-    expect(onSettledMutate).toHaveBeenCalledTimes(1)
-    expect(onSettledMutate).toHaveBeenCalledWith(
-      'result2',
-      null,
-      'todo',
-      undefined,
-      {
-        client: queryClient,
-        meta: undefined,
-        mutationKey: undefined,
-      },
-    )
+    expect(onSettledMutate).toHaveBeenCalledTimes(1)
+    expect(onSettledMutate).toHaveBeenCalledWith(
+      'result2',
+      null,
+      'todo',
+      undefined,
+      expect.objectContaining({ client: queryClient }),
+    )

@@
-    expect(onError).toHaveBeenCalledWith(error, 'todo', undefined, {
-      client: queryClient,
-      meta: undefined,
-      mutationKey: undefined,
-    })
+    expect(onError).toHaveBeenCalledWith(
+      error,
+      'todo',
+      undefined,
+      expect.objectContaining({ client: queryClient }),
+    )

@@
-    expect(onError).toHaveBeenCalledWith(mutateFnError, 'todo', undefined, {
-      client: queryClient,
-      meta: undefined,
-      mutationKey: undefined,
-    })
+    expect(onError).toHaveBeenCalledWith(
+      mutateFnError,
+      'todo',
+      undefined,
+      expect.objectContaining({ client: queryClient }),
+    )

Also applies to: 1060-1064, 1071-1076, 1114-1118, 1203-1207


1004-1077: Add a focused test that validates onMutateResult is threaded into callbacks

Currently, assertions only cover the new context object; they don’t exercise that the onMutate return value is forwarded as onMutateResult. Add a narrow test to lock this in.

Example test to append at the end of the file:

it('should pass onMutateResult to onSuccess/onSettled callbacks', async () => {
  const onSuccess = vi.fn()
  const onSettled = vi.fn()

  function Page() {
    const mutation = useMutation(() => ({
      mutationFn: async (v: number) => v * 2,
      onMutate: (v: number) => `optimistic:${v}`,
      onSuccess,
      onSettled,
    }))
    createEffect(() => {
      setActTimeout(() => mutation.mutate(2), 0)
    })
    return null
  }

  render(() => (
    <QueryClientProvider client={queryClient}>
      <Page />
    </QueryClientProvider>
  ))

  await vi.advanceTimersByTimeAsync(0)

  expect(onSuccess).toHaveBeenCalledWith(
    4,
    2,
    'optimistic:2',
    expect.any(Object),
  )
  expect(onSettled).toHaveBeenCalledWith(
    4,
    null,
    2,
    'optimistic:2',
    expect.any(Object),
  )
})
packages/solid-query/src/types.ts (1)

143-159: Document what “TScope” represents to reduce ambiguity

A short JSDoc on TScope (e.g., “additional runtime scope passed to mutateFn and callbacks; contains client, meta, mutationKey”) will help users migrating from “context”.

Suggested addition:

-export interface SolidMutationOptions<
+export interface SolidMutationOptions<
   TData = unknown,
   TError = DefaultError,
   TVariables = void,
-  TScope = unknown,
+  /**
+   * Additional runtime scope passed to mutateFn and callbacks.
+   * Typically includes { client, meta, mutationKey } from query-core.
+   */
+  TScope = unknown,
 > extends OmitKeyof<
packages/eslint-plugin-query/src/__tests__/mutation-property-order.rule.test.ts (1)

146-149: Align callback parameter lists with new onMutateResult signature

Update the examples to include the onMutateResult parameter and keep its position consistent: onError(error, variables, onMutateResult, scope) and onSettled(data, error, variables, onMutateResult, scope).

-        return 'onError: (error, variables, scope) => {\n  console.log("error:", error, "scope:", scope)\n}'
+        return 'onError: (error, variables, onMutateResult, scope) => {\n  console.log("error:", error, "scope:", scope, "onMutateResult:", onMutateResult)\n}'

-        return 'onSettled: (data, error, variables, scope) => {\n  console.log("settled", scope)\n}'
+        return 'onSettled: (data, error, variables, onMutateResult, scope) => {\n  console.log("settled", scope, onMutateResult)\n}'
-        onError: (error, variables, scope) => {
-          console.log(error, scope)
+        onError: (error, variables, onMutateResult, scope) => {
+          console.log(error, scope, onMutateResult)
         },
-        onSettled: (data, error, variables, scope) => {
-          console.log('settled', scope)
+        onSettled: (data, error, variables, onMutateResult, scope) => {
+          console.log('settled', scope, onMutateResult)
         },

Also applies to: 205-210

packages/query-devtools/src/Devtools.tsx (1)

2419-2431: Rename UI to “Scope” and keep a fallback to legacy state.context.

This prevents user confusion and preserves visibility for hydrated mutations that still carry context.

Apply:

-        <div class={cx(styles().detailsHeader, 'tsqd-query-details-header')}>
-          Context Details
+        <div class={cx(styles().detailsHeader, 'tsqd-query-details-header')}>
+          Scope Details
@@
-          <Explorer
-            label="Context"
+          <Explorer
+            label="Scope"
@@
-            value={activeMutation()!.state.scope}
+            value={
+              activeMutation()!.state.scope ??
+              // fallback for older persisted entries
+              // @ts-expect-error legacy
+              activeMutation()!.state.context
+            }

To ensure nothing else still reads state.context:

#!/bin/bash
rg -nS '\bstate\.context\b|Context Details|label="Context"' packages/query-devtools || true
packages/vue-query/src/mutationCache.ts (1)

10-19: Use the TypeScript override modifier on overridden methods.

Improves safety against base-class signature drift.

 export class MutationCache extends MC {
-  find<
+  override find<
     TData = unknown,
     TError = DefaultError,
     TVariables = any,
     TScope = unknown,
   >(
     filters: MaybeRefDeep<MutationFilters>,
   ): Mutation<TData, TError, TVariables, TScope> | undefined {
     return super.find(cloneDeepUnref(filters))
   }
packages/query-core/src/utils.ts (1)

173-200: Preserve type inference in matchMutation by threading generics.

Expose the same generics as MutationFilters/Mutation so predicates and call sites retain concrete types instead of falling back to any.

-export function matchMutation(
-  filters: MutationFilters,
-  mutation: Mutation<any, any>,
-): boolean {
+export function matchMutation<
+  TData = unknown,
+  TError = DefaultError,
+  TVariables = unknown,
+  TScope = unknown,
+>(
+  filters: MutationFilters<TData, TError, TVariables, TScope>,
+  mutation: Mutation<TData, TError, TVariables, TScope>,
+): boolean {

No behavioral change; improves editor IntelliSense and catches mismatches in predicates.

packages/query-core/src/queryClient.ts (1)

529-546: Optional: surface generics in getMutationDefaults for better inference.

Currently returns any-based generics; consider mirroring setMutationDefaults generics for callers who carry through concrete T types.

-export function getMutationDefaults(
-  mutationKey: MutationKey,
-): OmitKeyof<MutationObserverOptions<any, any, any, any>, 'mutationKey'> {
+export function getMutationDefaults<
+  TData = unknown,
+  TError = DefaultError,
+  TVariables = void,
+  TScope = unknown,
+>(
+  mutationKey: MutationKey,
+): OmitKeyof<MutationObserverOptions<TData, TError, TVariables, TScope>, 'mutationKey'> {
packages/vue-query/src/queryClient.ts (1)

472-476: Stronger typing for getMutationDefaults (optional)

Expose generics here too to mirror setMutationDefaults and preserve TScope across reads.

-  getMutationDefaults(
-    mutationKey: MaybeRefDeep<MutationKey>,
-  ): MutationObserverOptions<any, any, any, any> {
+  getMutationDefaults<
+    TData = unknown,
+    TError = DefaultError,
+    TVariables = void,
+    TScope = unknown
+  >(
+    mutationKey: MaybeRefDeep<MutationKey>,
+  ): MutationObserverOptions<TData, TError, TVariables, TScope> {
examples/react/optimistic-updates-cache/src/pages/index.tsx (2)

9-10: Remove unused useQueryClient import and variable

Not used after switching to context.client.

   useQuery,
-  useQueryClient,
 } from '@tanstack/react-query'
@@
-  const queryClient = useQueryClient()

Also applies to: 35-35


75-86: Prefer todoListOptions.queryKey for consistency

Use the same queryKey source everywhere to avoid drift.

-      if (onMutateResult?.previousTodos) {
-        context.client.setQueryData<Todos>(
-          ['todos'],
-          onMutateResult.previousTodos,
-        )
-      }
+      if (onMutateResult?.previousTodos) {
+        context.client.setQueryData<Todos>(
+          todoListOptions.queryKey,
+          onMutateResult.previousTodos,
+        )
+      }
@@
-    onSettled: (data, error, variables, onMutateResult, context) =>
-      context.client.invalidateQueries({ queryKey: ['todos'] }),
+    onSettled: (data, error, variables, onMutateResult, context) =>
+      context.client.invalidateQueries({ queryKey: todoListOptions.queryKey }),
docs/framework/angular/reference/functions/mutationoptions.md (1)

49-49: Clarify what TScope represents in the docs.

Readers may confuse callback “context” with the new scope type. Consider expanding the bullet to define TScope explicitly.

-• **TScope** = `unknown`
+• **TScope** = `unknown` — the type of the value returned from `onMutate` (often called the “onMutateResult”) that is passed to `onSuccess`, `onError`, and `onSettled`.
docs/framework/react/guides/optimistic-updates.md (2)

102-106: Add a brief hint that “context” is a MutationFunctionContext.

Helps readers understand where client comes from without hunting.

 useMutation({
   mutationFn: updateTodo,
   // When mutate is called:
-  onMutate: async (newTodo, context) => {
+  // context is a MutationFunctionContext (client, meta, mutationKey)
+  onMutate: async (newTodo, context) => {

Also applies to: 108-115, 117-124


137-147: Mirror the same context hint in the second example.

Keeps both examples self-explanatory.

 useMutation({
   mutationFn: updateTodo,
   // When mutate is called:
-  onMutate: async (newTodo, context) => {
+  // context is a MutationFunctionContext (client, meta, mutationKey)
+  onMutate: async (newTodo, context) => {

Also applies to: 149-156, 159-161

packages/react-query/src/__tests__/mutationOptions.test-d.tsx (1)

74-91: Strengthen context shape assertions.

Also assert key properties on the context to lock its public surface.

   it('should infer context type correctly', () => {
     mutationOptions<number>({
       mutationFn: (_variables, context) => {
         expectTypeOf(context).toEqualTypeOf<MutationFunctionContext>()
+        expectTypeOf(context.client).toMatchTypeOf<QueryClient>()
+        expectTypeOf(context.meta).toEqualTypeOf<Record<string, unknown> | undefined>()
+        expectTypeOf(context.mutationKey).toEqualTypeOf<unknown[] | undefined>()
         return Promise.resolve(5)
       },
       mutationKey: ['key'],
       onMutate: (_variables, context) => {
         expectTypeOf(context).toEqualTypeOf<MutationFunctionContext>()
+        expectTypeOf(context.client.cancelQueries).toBeFunction()
       },
       onSuccess: (_data, _variables, _onMutateResult, context) => {
         expectTypeOf(context).toEqualTypeOf<MutationFunctionContext>()
       },
       onError: (_error, _variables, _onMutateResult, context) => {
         expectTypeOf(context).toEqualTypeOf<MutationFunctionContext>()
       },
       onSettled: (_data, _error, _variables, _onMutateResult, context) => {
         expectTypeOf(context).toEqualTypeOf<MutationFunctionContext>()
       },
     })
   })
docs/framework/svelte/reference/type-aliases/createbasemutationresult.md (1)

34-34: Clarify what TScope represents in the docs

Add a short note so users understand provenance and flow.

Apply this doc tweak:

-• **TScope** = `unknown`
+• **TScope** = `unknown` — value returned from `onMutate`, passed as the 4th arg to callbacks (`onError`, `onSuccess`, `onSettled`), and stored on `mutation.state.scope` (legacy `context` is deprecated).
packages/query-core/src/__tests__/mutations.test.tsx (3)

56-60: Make MutationFunctionContext assertion future-proof

toHaveBeenCalledWith on a concrete object is brittle if more fields (e.g., signal) are added.

Apply:

-    expect(fn).toHaveBeenCalledWith('vars', {
-      client: queryClient,
-      meta: undefined,
-      mutationKey: key,
-    })
+    expect(fn).toHaveBeenCalledWith(
+      'vars',
+      expect.objectContaining({
+        client: queryClient,
+        mutationKey: key,
+        meta: undefined,
+      }),
+    )

71-72: Scope state assertions look good; consider DRY helper

Asserting both context and scope across many states is repetitive. A tiny helper would reduce noise and make future changes (e.g., dropping legacy context) easier.

Example helper (outside the test blocks):

function expectStateScopeEqual(state: { scope?: any; context?: any }, expected: any) {
  expect(state.scope).toEqual(expected)
  expect(state.context).toEqual(expected) // until legacy is removed
}

Then replace duplicated pairs with one call.

Also applies to: 100-101, 121-122, 142-143, 181-182, 202-203, 223-224, 244-245, 285-286, 299-300, 315-316, 330-331


796-796: Remove stray console.log in test

Avoids noisy CI output.

-      console.log(results)
+      // (removed) console.log(results)
packages/vue-query/src/useMutation.ts (1)

27-34: Consider aligning the generic name with core (optional).

To reduce cognitive load alongside “Mutation Scopes” (scope: { id }), consider renaming local generic TScope to TOnMutateResult for symmetry with @tanstack/query-core.

-type MutationResult<TData, TError, TVariables, TScope> = …
-type UseMutationOptionsBase<TData, TError, TVariables, TScope> = …
-export type UseMutationOptions<…, TScope = unknown> = …
-type MutateSyncFunction<…, TScope = unknown> = …
-export type UseMutationReturnType<…, TScope, TResult = MutationResult<…, TScope>> = …
-export function useMutation<…, TScope = unknown>(…): UseMutationReturnType<…, TScope> {
+type MutationResult<TData, TError, TVariables, TOnMutateResult> = …
+type UseMutationOptionsBase<TData, TError, TVariables, TOnMutateResult> = …
+export type UseMutationOptions<…, TOnMutateResult = unknown> = …
+type MutateSyncFunction<…, TOnMutateResult = unknown> = …
+export type UseMutationReturnType<…, TOnMutateResult, TResult = MutationResult<…, TOnMutateResult>> = …
+export function useMutation<…, TOnMutateResult = unknown>(…): UseMutationReturnType<…, TOnMutateResult> {

Also applies to: 39-41, 55-61

docs/framework/angular/guides/mutations.md (2)

225-241: Guard against undefined cache data in setQueryData updaters

If old is undefined, spreading or calling .map/.filter throws. Add a fallback.

Apply these diffs:

-    context.client.setQueryData(['todos'], (old) => [...old, optimisticTodo])
+    context.client.setQueryData(['todos'], (old) => [...(old ?? []), optimisticTodo])
-    context.client.setQueryData(['todos'], (old) =>
-      old.map((todo) =>
+    context.client.setQueryData(['todos'], (old) =>
+      (old ?? []).map((todo) =>
         todo.id === onMutateResult.optimisticTodo.id ? result : todo,
       ),
    )
-    context.client.setQueryData(['todos'], (old) =>
-      old.filter((todo) => todo.id !== onMutateResult.optimisticTodo.id),
+    context.client.setQueryData(['todos'], (old) =>
+      (old ?? []).filter((todo) => todo.id !== onMutateResult.optimisticTodo.id),
    )

Also applies to: 240-242


227-229: Clarify terminology: “onMutateResult” vs “scope” in comment

This comment says “Return scope…”, while the rest of this guide calls the onMutate return value “onMutateResult”. Align to avoid confusion.

Apply this diff:

-    // Return scope with the optimistic todo
+    // Return onMutateResult with the optimistic todo
packages/query-core/src/mutation.ts (2)

213-231: Optional: avoid double “pending” dispatch by incorporating scope upfront

Currently, you dispatch pending (without scope), then potentially dispatch pending again after onMutate resolves. Consider awaiting onMutate first and then dispatch a single pending with both variables and scope to reduce churn. Keep if immediate “pending” is required for UX.

Example shape:

- this.#dispatch({ type: 'pending', variables, isPaused })
- await this.#mutationCache.config.onMutate?.(variables, this as Mutation<unknown, unknown, unknown, unknown>)
- const scope = await this.options.onMutate?.(variables, mutationFnContext)
- if (scope !== this.state.scope) {
-   this.#dispatch({ type: 'pending', scope, variables, isPaused })
- }
+ await this.#mutationCache.config.onMutate?.(variables, this as Mutation<unknown, unknown, unknown, unknown>)
+ const scope = await this.options.onMutate?.(variables, mutationFnContext)
+ this.#dispatch({ type: 'pending', scope, variables, isPaused })

178-183: DRY: factor context creation into a shared helper

MutationFunctionContext is constructed here and again in MutationObserver. Extract a small helper to keep them in sync.

Proposed helper (new file or colocated util):

// packages/query-core/src/context.ts
import type { QueryClient } from './queryClient'
import type { MutationFunctionContext, MutationOptions } from './types'

export function buildMutationFunctionContext(
  client: QueryClient,
  options: Pick<MutationOptions<any, any, any, any>, 'meta' | 'mutationKey'>,
): MutationFunctionContext {
  return { client, meta: options.meta, mutationKey: options.mutationKey }
}

Apply this diff here:

-    const mutationFnContext = {
-      client: this.#client,
-      meta: this.options.meta,
-      mutationKey: this.options.mutationKey,
-    } satisfies MutationFunctionContext
+    const mutationFnContext = buildMutationFunctionContext(this.#client, this.options)

And use the same helper in MutationObserver (see comment there).

Also applies to: 242-247

packages/query-core/src/mutationObserver.ts (1)

145-157: DRY: reuse a shared MutationFunctionContext builder

Same context object is hand-built here and in Mutation. Prefer a shared helper to avoid drift.

Apply this diff (after adding the helper suggested in mutation.ts review):

-        const context = {
-          client: this.#client,
-          meta: this.options.meta,
-          mutationKey: this.options.mutationKey,
-        } satisfies MutationFunctionContext
+        const context = buildMutationFunctionContext(this.#client, this.options)
packages/query-core/src/mutationCache.ts (2)

195-208: find typing is fine; consider removing the cast via a type guard (optional).

If matchMutation exposed a type predicate, the as Mutation<...> cast could go away, improving inference. Optional—current approach is acceptable.


234-235: Guard against empty scope ids to avoid accidental global scoping (optional).

Treating '' as a valid scope key can unintentionally serialize unrelated mutations under the empty id.

Apply this small guard:

-function scopeFor(mutation: Mutation<any, any, any, any>) {
-  return mutation.options.scope?.id
-}
+function scopeFor(mutation: Mutation<any, any, any, any>) {
+  const id = mutation.options.scope?.id
+  return typeof id === 'string' && id.length > 0 ? id : undefined
+}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ccedf33 and cb89bfe.

📒 Files selected for processing (51)
  • docs/framework/angular/guides/mutations.md (4 hunks)
  • docs/framework/angular/guides/optimistic-updates.md (3 hunks)
  • docs/framework/angular/reference/functions/injectmutation.md (3 hunks)
  • docs/framework/angular/reference/functions/mutationoptions.md (2 hunks)
  • docs/framework/angular/reference/interfaces/basemutationnarrowing.md (6 hunks)
  • docs/framework/angular/reference/interfaces/createmutationoptions.md (2 hunks)
  • docs/framework/angular/reference/type-aliases/createbasemutationresult.md (3 hunks)
  • docs/framework/angular/reference/type-aliases/createmutateasyncfunction.md (2 hunks)
  • docs/framework/angular/reference/type-aliases/createmutatefunction.md (2 hunks)
  • docs/framework/angular/reference/type-aliases/createmutationresult.md (2 hunks)
  • docs/framework/react/guides/mutations.md (4 hunks)
  • docs/framework/react/guides/optimistic-updates.md (3 hunks)
  • docs/framework/react/reference/useMutation.md (3 hunks)
  • docs/framework/svelte/reference/functions/createmutation.md (2 hunks)
  • docs/framework/svelte/reference/type-aliases/createbasemutationresult.md (3 hunks)
  • docs/framework/svelte/reference/type-aliases/createmutateasyncfunction.md (2 hunks)
  • docs/framework/svelte/reference/type-aliases/createmutatefunction.md (2 hunks)
  • docs/framework/svelte/reference/type-aliases/createmutationoptions.md (2 hunks)
  • docs/framework/svelte/reference/type-aliases/createmutationresult.md (2 hunks)
  • docs/reference/MutationCache.md (2 hunks)
  • examples/react/optimistic-updates-cache/src/pages/index.tsx (3 hunks)
  • packages/angular-query-experimental/src/__tests__/mutation-options.test-d.ts (2 hunks)
  • packages/angular-query-experimental/src/inject-mutation.ts (5 hunks)
  • packages/angular-query-experimental/src/mutation-options.ts (2 hunks)
  • packages/angular-query-experimental/src/types.ts (7 hunks)
  • packages/eslint-plugin-query/src/__tests__/mutation-property-order.rule.test.ts (2 hunks)
  • packages/query-core/src/__tests__/hydration.test.tsx (1 hunks)
  • packages/query-core/src/__tests__/mutationCache.test.tsx (7 hunks)
  • packages/query-core/src/__tests__/mutationObserver.test.tsx (2 hunks)
  • packages/query-core/src/__tests__/mutations.test.tsx (17 hunks)
  • packages/query-core/src/__tests__/utils.test.tsx (1 hunks)
  • packages/query-core/src/mutation.ts (12 hunks)
  • packages/query-core/src/mutationCache.ts (4 hunks)
  • packages/query-core/src/mutationObserver.ts (8 hunks)
  • packages/query-core/src/queryClient.ts (1 hunks)
  • packages/query-core/src/types.ts (8 hunks)
  • packages/query-core/src/utils.ts (2 hunks)
  • packages/query-devtools/src/Devtools.tsx (1 hunks)
  • packages/react-query/src/__tests__/mutationOptions.test-d.tsx (2 hunks)
  • packages/react-query/src/__tests__/useMutation.test.tsx (4 hunks)
  • packages/react-query/src/mutationOptions.ts (1 hunks)
  • packages/react-query/src/types.ts (1 hunks)
  • packages/react-query/src/useMutation.ts (2 hunks)
  • packages/solid-query/src/__tests__/useMutation.test.tsx (4 hunks)
  • packages/solid-query/src/types.ts (1 hunks)
  • packages/solid-query/src/useMutation.ts (1 hunks)
  • packages/svelte-query/src/createMutation.ts (1 hunks)
  • packages/svelte-query/src/types.ts (1 hunks)
  • packages/vue-query/src/mutationCache.ts (1 hunks)
  • packages/vue-query/src/queryClient.ts (1 hunks)
  • packages/vue-query/src/useMutation.ts (3 hunks)
🔇 Additional comments (65)
packages/solid-query/src/useMutation.ts (2)

15-24: TScope rename is correctly propagated through the Solid wrapper

Generics, observer instantiation, and exposed types align with query-core. No runtime changes—good.

Also applies to: 26-27, 31-32, 39-40


15-24: Quick sweep for leftover TContext references in Solid package

Sanity-check there are no stale TContext references that could confuse consumers or shadow new types.

#!/bin/bash
# Look for any remaining TContext references in Solid package
rg -n --type=ts '\bTContext\b' packages/solid-query -C2

# Ensure MutationObserver generics are consistently using 4 params and TScope in Solid
rg -n --type=ts 'MutationObserver<[^>]+>' packages/solid-query/src -C2
packages/solid-query/src/types.ts (1)

143-159: Type-level refactor to TScope looks consistent and non-breaking

All mutation-related public types now use TScope; MutateFunction parameter plumbs through correctly; mutate vs mutateAsync split preserved. Looks good.

Also applies to: 160-175, 176-186, 188-194

packages/react-query/src/mutationOptions.ts (1)

8-16: TScope rename looks correct; overloads remain type-safe

All three overloads consistently replace TContext with TScope. No runtime changes.

Also applies to: 22-29, 33-37

docs/framework/angular/reference/type-aliases/createmutateasyncfunction.md (1)

6-11: TScope rename LGTM

Docs correctly reflect TContext → TScope on the alias and type params.

Also applies to: 21-21

docs/framework/angular/reference/interfaces/createmutationoptions.md (1)

6-11: TScope rename LGTM

Extends clause and type params are consistent.

Also applies to: 20-20

docs/framework/angular/reference/type-aliases/createmutationresult.md (1)

6-12: TScope rename LGTM

Signature and dependent types updated consistently.

Also applies to: 22-25

docs/framework/angular/reference/type-aliases/createbasemutationresult.md (1)

6-16: TScope rename LGTM

Override target and mutate/mutateAsync types all reflect TScope.

Also applies to: 22-24, 34-34

docs/framework/angular/reference/interfaces/basemutationnarrowing.md (1)

6-6: TScope rename looks consistent across the interface and nested result types.

No functional issues spotted in the doc snippet.

Run to catch any lingering TContext references in Angular docs:

#!/bin/bash
rg -n --glob 'docs/framework/angular/**' -S '\bTContext\b|MutationObserver(Result|.*Result)<[^>]*TContext' || true

Also applies to: 16-16, 28-34, 53-59, 78-84, 103-109

packages/svelte-query/src/types.ts (1)

90-94: Svelte types: TScope propagation LGTM.

  • CreateMutationOptions/Result/Mutate types correctly switch to TScope.
  • Async mutate aliasing to core MutateFunction is sound.

Nothing blocking.

Also applies to: 100-103, 106-111, 116-122, 129-131

packages/svelte-query/src/createMutation.ts (1)

17-18: TScope wiring through createMutation is correct.

Observer generic parameters and returned types align with the new TScope signatures.

Also applies to: 19-21, 26-27, 30-31

packages/query-core/src/__tests__/utils.test.tsx (1)

426-431: Test update to pass client into Mutation constructor is correct.

Matches the new constructor contract; assertions read well.

packages/react-query/src/__tests__/useMutation.test.tsx (1)

528-533: Callback “context arg” assertions are consistent and correct.

  • onMutate now receives (variables, { client, meta, mutationKey }).
  • onSuccess/onError/onSettled include the 4th descriptor param consistently.

Looks good.

Also applies to: 986-991, 998-1002, 1010-1014, 1022-1026, 1060-1064, 1142-1146

packages/query-core/src/__tests__/hydration.test.tsx (1)

544-549: Hydration: new 4th callback param assertion matches the API.

The descriptor object with client/meta/mutationKey is validated correctly.

packages/query-core/src/__tests__/mutationCache.test.tsx (1)

30-31: “scope” terminology aligned across mutationCache tests.

Return value from onMutate and the 3rd arg to callbacks updated coherently.

Also applies to: 42-43, 51-53, 112-113, 124-126, 190-191

packages/vue-query/src/mutationCache.ts (1)

15-19: Rename to TScope in generics looks good.

Consistent with core’s Mutation/MutationFilters generics and keeps Vue wrapper aligned.

packages/query-core/src/utils.ts (2)

46-52: TScope rename in MutationFilters is correct.

Type-only change; no runtime impact.


59-60: Predicate now typed with TScope—good.

Keeps user predicates accurately typed against the scoped Mutation instance.

docs/framework/svelte/reference/type-aliases/createmutationresult.md (2)

6-12: Docs: TScope rename reads well.

Header and snippet correctly reflect TScope.


28-29: Verify “Defined in” anchors after merge.

Line numbers often shift; ensure the link still points to the typedef.

#!/bin/bash
# Confirm typedef location for CreateMutationResult
rg -n "^export type CreateMutationResult<" packages/svelte-query/src/types.ts
packages/query-core/src/queryClient.ts (1)

511-522: setMutationDefaults: TScope migration looks correct.

Signature and options typing now align with the new generic parameter.

docs/framework/svelte/reference/type-aliases/createmutationoptions.md (2)

6-13: Docs: TScope update matches core types.

Type alias and snippet are consistent with MutationObserverOptions’s new generic parameter.


27-29: Verify “Defined in” anchor.

Ensure the link remains accurate post-merge.

#!/bin/bash
rg -n "^export type CreateMutationOptions<" packages/svelte-query/src/types.ts
docs/framework/svelte/reference/type-aliases/createmutateasyncfunction.md (2)

6-11: Docs: async mutate function now parameterized by TScope—looks good.

Keeps MutateFunction wiring consistent with the rest of the TScope changes.


23-26: Check “Defined in” anchor.

Same note on potential line drift.

#!/bin/bash
rg -n "^export type CreateMutateAsyncFunction<" packages/svelte-query/src/types.ts
packages/angular-query-experimental/src/__tests__/mutation-options.test-d.ts (3)

11-14: Importing MutationFunctionContext is appropriate.

Aligns Angular tests with the new context argument.


62-75: onMutateResult flow is validated correctly.

Good check that onSuccess receives { name: string } | undefined.


77-95: Context threaded to mutateFn and callbacks—nice coverage.

Covers mutateFn, onMutate, onSuccess, onError, and onSettled parameter positions and types.

packages/vue-query/src/queryClient.ts (1)

459-465: Types rename LGTM (TContext → TScope) for setMutationDefaults

Signature and MutationObserverOptions generics are updated consistently. No runtime impact.

docs/framework/angular/reference/functions/injectmutation.md (1)

9-13: Signature rename to TScope is consistent with implementation

Docs reflect the API change accurately.

packages/angular-query-experimental/src/inject-mutation.ts (1)

45-59: TScope migration looks correct

Generic rename and type wiring (MutationObserver, CreateMutateFunction/Result) are consistent. No behavioral regressions spotted.

Also applies to: 72-83, 101-107, 166-172

docs/reference/MutationCache.md (1)

31-41: Callbacks parameter rename to scope reads well

The onError/onSuccess/onSettled signatures now use scope; matches PR intent.

docs/framework/svelte/reference/type-aliases/createmutatefunction.md (1)

6-10: TScope rename in Svelte type alias is consistent

Docs align with types; DefaultError default is correct.

Also applies to: 20-27

packages/query-core/src/__tests__/mutationObserver.test.tsx (1)

320-339: Tests cover new options argument and callback order

Great assertions for the extra options object and argument order in success/error paths.

Also applies to: 365-383

packages/react-query/src/useMutation.ts (1)

23-27: TScope generic is correctly plumbed through useMutation. LGTM.

Type-only changes look consistent across options, observer, and mutate types; runtime remains unchanged.

Also applies to: 32-33, 50-51

docs/framework/angular/reference/functions/mutationoptions.md (1)

9-12: Mechanical rename to TScope is accurate.

Signatures and return types are updated consistently.

Also applies to: 55-56, 61-62

packages/angular-query-experimental/src/mutation-options.ts (1)

38-47: TScope rename across overloads/impl looks correct.

Overloads remain source-compatible; implementation stays runtime no-op.

Also applies to: 52-59, 94-98

docs/framework/svelte/reference/type-aliases/createbasemutationresult.md (3)

6-6: Rename to TScope looks good

Public docs now align with the API direction (context → scope).


9-13: Propagate TScope through nested types

Good consistency across MutationObserverResult and mutate signatures.


23-24: mutateAsync generic alignment is correct

Matches the mutate signature and new fourth generic.

packages/react-query/src/types.ts (5)

202-210: UseMutateFunction mirrors core MutateFunction params

Using Parameters<> preserves the parameter list while fixing return type to void for mutate. Good.


211-217: UseMutateAsyncFunction alias is consistent

Async variant cleanly aliases core MutateFunction with TScope.


218-227: UseBaseMutationResult wiring is correct

TScope is consistently threaded through result, mutate, and mutateAsync.


228-234: UseMutationResult type alias updated correctly

Alias now forwards TScope as intended.


191-200: TScope generic on UseMutationOptions

Rename and threading into MutationObserverOptions looks correct. Please ensure 3-generic call-sites still infer as before (fourth defaults to unknown).

You can quickly scan for 3-arg usages to confirm test coverage:

#!/bin/bash
rg -nP '\bUseMutationOptions<\s*[^,>]+,\s*[^,>]+,\s*[^,>]+\s*>' -g '!**/node_modules/**'
packages/query-core/src/__tests__/mutations.test.tsx (2)

779-781: 4th callback arg renamed to scope — correct usage

Matches the new contract; assertion string is consistent.


831-833: Error-path onSettled receives scope — correct

Covers the de-duped callback path with scope data.

docs/framework/react/guides/mutations.md (2)

146-162: Example “Mutation Side Effects” looks correct with new parameters.

Signatures match the new API: onMutate(variables, context) and callbacks receiving (…, onMutateResult, context).


297-340: Optimistic update flow with context.client looks good.

The usage of context.client for cancelQueries/setQueryData and passing onMutateResult through callbacks is consistent with the new contract.

packages/vue-query/src/useMutation.ts (1)

27-34: TScope adoption and wiring to core generics look correct.

Types line up with core’s <TData, TError, TVariables, TOnMutateResult>, and the wrapper correctly exposes mutate (void) and mutateAsync (Promise).

Also applies to: 32-34, 39-41, 46-49, 55-61, 67-74, 95-99, 117-119

packages/query-core/src/types.ts (3)

1094-1099: Introduce MutationFunctionContext: good shape and minimal surface.

client, mutationKey, meta cover the typical needs; consistent with docs.

Also applies to: 1101-1104


1105-1117: Lifecycle signatures look consistent and safe (onMutateResult is unioned with undefined).

Changing to TOnMutateResult | undefined removes the previous unsound non-null assertions.

Also applies to: 1118-1143


1154-1179: Confirm downstream wrappers align with Promise-returning mutate.

Core exposes mutate as Promise-returning (for wrappers to map to mutateAsync). React/Vue/Svelte wrappers should keep exposing mutate void and pipe state.mutate to mutateAsync. Looks already correct for Vue in this PR; please ensure others match.

Would you like a quick scan script to spot any wrapper that still re-exports Promise-returning mutate?

Also applies to: 1181-1189, 1249-1250

packages/query-core/src/mutation.ts (2)

178-183: Good: well-typed MutationFunctionContext construction

Using satisfies MutationFunctionContext ensures structural conformance without widening types.


32-37: Deprecation note looks good

Clear guidance and timeline for contextscope migration in state.

packages/query-core/src/mutationObserver.ts (1)

17-32: API surface rename to TScope: looks consistent

Generic renames, result typing, and listener signatures align with the new “scope” concept.

Also applies to: 88-101, 129-143

packages/query-core/src/mutationCache.ts (3)

13-37: Rename to “scope” looks good—please confirm cross-API consistency and docs alignment.

Signatures now pass scope to onError/onSuccess/onSettled. Double-check:

  • All adapters (React/Vue/Svelte/Angular/Solid) forward scope consistently to their public callbacks.
  • Docs/examples reference scope (and, where applicable, onMutateResult) and call out deprecation of context.

You can quickly scan for lingering “context” in mutation callback parameters:

#!/bin/bash
rg -nP --glob='packages/**/src/**' '\bon(Error|Success|Settled)\s*\([^)]*\bcontext\b'
rg -nP --glob='docs/**' '\bcontext\b' -C2

96-103: Threading QueryClient into Mutation is sensible. Verify Mutation’s config type includes it.

Passing client into new Mutation({...}) assumes MutationConfig accepts client: QueryClient. Please confirm the constructor and type are updated accordingly to avoid structural typing pitfalls.

#!/bin/bash
# Show Mutation constructor/config shape and confirm 'client' is present:
rg -nP 'class\s+Mutation\b' packages/query-core/src -n -C3
rg -nP 'interface\s+MutationConfig|type\s+MutationConfig' packages/query-core/src -n -C3
rg -nP 'constructor\s*\(' packages/query-core/src/mutation.ts -n -C3

49-58: Potential generic arity drift for MutationObserver.

These notify types use MutationObserver<any, any, any>. If MutationObserver now has <TData, TError, TVariables, TScope>, this will compile-error. If it’s still 3 params, ignore.

#!/bin/bash
# Verify MutationObserver generic arity and usages:
rg -nP '^export\s+(?:class|interface|type)\s+MutationObserver\s*<([^>]*)>' packages/query-core/src -C1
rg -nP '\bMutationObserver<[^>]*>' packages/query-core/src -n
packages/angular-query-experimental/src/types.ts (6)

162-171: TScope migration for CreateMutationOptions aligns with core.

The generic rename and downstream mapping to MutationObserverOptions<..., TScope> look correct.


197-207: CreateBaseMutationResult override is consistent with TScope propagation.

Override of mutate/mutateAsync with TScope threading looks good.


212-221: Status-narrowed mutation result updates are coherent.

CreateStatusBasedMutationResult<..., TScope> wiring matches the base result changes.


228-296: Type guards preserve TScope across narrows.

this: CreateMutationResult<..., TScope> ensures scope sticks through isSuccess/isError/isPending/isIdle. Looks correct.


301-315: Top-level CreateMutationResult threads TScope end-to-end.

No issues spotted with the TState default and MapToSignals projection.


175-193: Default TVariables differs across aliases—confirm this is intentional.

Here, CreateMutateFunction/CreateMutateAsyncFunction use TVariables = void, while CreateBaseMutationResult below defaults TVariables = unknown. Consider aligning defaults or documenting why they differ to prevent surprising inference.

#!/bin/bash
# Check defaults for TVariables across Angular types:
rg -nP 'TVariables\s*=\s*(void|unknown)' packages/angular-query-experimental/src/types.ts -n
# Compare with core:
rg -nP 'type\s+MutateFunction<|interface\s+MutationObserverResult<' packages/query-core/src -n -C2

Copy link
Collaborator

@TkDodo TkDodo left a comment

Choose a reason for hiding this comment

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

There are still several places where scope is referenced, please have a look at them 🙏

@joshuaellis joshuaellis force-pushed the feat/mutationfn-context branch from cb89bfe to 5deb6f4 Compare September 9, 2025 15:22
* @deprecated `context` was renamed to `scope`. This will be removed in the next major version. Use the `scope` property instead.
*/
context: TOnMutateResult | undefined
scope: TOnMutateResult | undefined
Copy link
Author

Choose a reason for hiding this comment

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

@TkDodo should we rename this variable then? or do you think this is fine, this is the only hangover of scope left afaik

@joshuaellis joshuaellis force-pushed the feat/mutationfn-context branch from 5deb6f4 to 60d0300 Compare September 9, 2025 15:26
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
packages/svelte-query/src/createMutation.ts (1)

36-41: Fix leaked subscriptions in query/mutation creators

  • packages/svelte-query/src/createMutation.ts (L36–41): move optionsStore.subscribe into the readable setup callback and unsubscribe on teardown
-  optionsStore.subscribe(($options) => {
-    mutate = (variables, mutateOptions) => {
-      observer.mutate(variables, mutateOptions).catch(noop)
-    }
-    observer.setOptions($options)
-  })
-
-  const result = readable(observer.getCurrentResult(), (set) => {
-    return observer.subscribe(notifyManager.batchCalls((val) => set(val)))
-  })
+  const result = readable(observer.getCurrentResult(), (set) => {
+    // Subscribe to options with the store lifecycle to avoid leaks
+    const unsubscribeOptions = optionsStore.subscribe(($options) => {
+      mutate = (variables, mutateOptions) => {
+        observer.mutate(variables, mutateOptions).catch(noop)
+      }
+      observer.setOptions($options)
+    })
+    const unsubscribeObserver = observer.subscribe(
+      notifyManager.batchCalls((val) => set(val)),
+    )
+    return () => {
+      unsubscribeObserver()
+      unsubscribeOptions?.()
+    }
+  })
  • packages/svelte-query/src/createBaseQuery.ts (L58–60): wrap defaultedOptionsStore.subscribe inside the readable teardown pattern and clean up
  • packages/svelte-query/src/createQueries.ts (L232–234): wrap defaultedQueriesStore.subscribe inside the readable setup/cleanup
docs/framework/angular/reference/functions/injectmutation.md (1)

21-28: Fix default type for TError (DefaultError vs Error)

Docs show TError = Error, but implementation uses DefaultError. Mismatch can confuse users.

Apply:

-• **TError** = `Error`
+• **TError** = `DefaultError`
docs/framework/angular/guides/mutations.md (1)

251-253: Use this.mutation inside class method

mutation is not in scope; should reference the class property.

-    mutation.mutate({ title: 'title' })
+    this.mutation.mutate({ title: 'title' })
packages/query-core/src/mutationCache.ts (1)

58-67: Type mismatch: MutationObserver generics inconsistent (3 vs 4)

MutationObserver now has 4 generics; two places still use 3.

 interface NotifyEventMutationObserverAdded extends NotifyEvent {
   type: 'observerAdded'
   mutation: Mutation<any, any, any, any>
-  observer: MutationObserver<any, any, any>
+  observer: MutationObserver<any, any, any, any>
 }
@@
 interface NotifyEventMutationObserverRemoved extends NotifyEvent {
   type: 'observerRemoved'
   mutation: Mutation<any, any, any, any>
-  observer: MutationObserver<any, any, any>
+  observer: MutationObserver<any, any, any, any>
 }

Also applies to: 69-73

♻️ Duplicate comments (8)
docs/framework/svelte/reference/functions/createmutation.md (1)

19-19: Fix default TError in docs (DefaultError, not Error)

Docs should mirror implementation defaults.

Apply:

-• **TError** = `Error`
+• **TError** = `DefaultError`
docs/framework/angular/guides/optimistic-updates.md (3)

90-104: Guard setQueryData updater against undefined old

old can be undefined; spread will throw. Use nullish coalescing.

-    context.client.setQueryData(['todos'], (old) => [...old, newTodo])
+    context.client.setQueryData(['todos'], (old) => [ ...(old ?? []), newTodo ])

147-149: Return/await invalidation for per-id example

-  onSettled: (newTodo, error, variables, onMutateResult, context) => {
-    context.client.invalidateQueries({ queryKey: ['todos', newTodo.id] })
-  },
+  onSettled: async (newTodo, _e, _v, _r, context) => {
+    return await context.client.invalidateQueries({ queryKey: ['todos', newTodo.id] })
+  },

110-112: Return/await invalidation to keep mutation pending until refetch finishes

Make onSettled async and return the Promise.

-  onSettled: (data, error, variables, onMutateResult, context) => {
-    context.client.invalidateQueries({ queryKey: ['todos'] })
-  },
+  onSettled: async (_d, _e, _v, _r, context) => {
+    return await context.client.invalidateQueries({ queryKey: ['todos'] })
+  },
docs/reference/MutationCache.md (1)

53-54: Clarify that global onMutate return is ignored

Current text is still ambiguous. Suggest explicit wording to avoid implying propagation.

-– `onMutate` does not allow returning a result.
+– The return value of `onMutate` on the global MutationCache is ignored and not propagated (no onMutateResult is passed on).
docs/framework/react/guides/mutations.md (2)

190-214: Fix undefined identifier: destructure mutate from useMutation

mutate(...) is used without being declared.

-useMutation({
+const { mutate } = useMutation({
   mutationFn: addTodo,
   onSuccess: (data, variables, onMutateResult, context) => {
     // I will fire first
   },
   onError: (error, variables, onMutateResult, context) => {
     // I will fire first
   },
   onSettled: (data, error, variables, onMutateResult, context) => {
     // I will fire first
   },
 })
 
 mutate(todo, {

227-243: Same issue in “Consecutive mutations” example

Declare mutate before using it.

-useMutation({
+const { mutate } = useMutation({
   mutationFn: addTodo,
   onSuccess: (data, variables, onMutateResult, context) => {
     // Will be called 3 times
   },
 })
 
 const todos = ['Todo 1', 'Todo 2', 'Todo 3']
 todos.forEach((todo) => {
   mutate(todo, {
docs/framework/react/reference/useMutation.md (1)

67-81: Fix onMutate signature and clarify propagation target

Docs omit the context param and understate propagation.

-- `onMutate: (variables: TVariables) => Promise<TOnMutateResult | void> | TOnMutateResult | void`
+- `onMutate: (variables: TVariables, context: MutationFunctionContext) => Promise<TOnMutateResult | void> | TOnMutateResult | void`
@@
-  - The value returned from this function will be passed to both the `onError` and `onSettled` functions in the event of a mutation failure and can be useful for rolling back optimistic updates.
+  - The value returned from this function will be passed to `onSuccess`, `onError`, and `onSettled`. This enables optimistic updates and rollbacks.
🧹 Nitpick comments (10)
packages/eslint-plugin-query/src/__tests__/mutation-property-order.rule.test.ts (1)

146-146: Undefined identifier in onError fixture: use onMutateResult instead of scope.

The logged value should reference the callback param. Using scope is inconsistent with the new API and would be undefined if executed.

Apply this diff:

-return 'onError: (error, variables, onMutateResult) => {\n  console.log("error:", error, "onMutateResult:", scope)\n}'
+return 'onError: (error, variables, onMutateResult) => {\n  console.log("error:", error, "onMutateResult:", onMutateResult)\n}'
packages/solid-query/src/useMutation.ts (1)

42-44: State type updated; minor init nit: avoid double getCurrentResult().

Micro-alloc reduction and readability.

Apply:

-  const [state, setState] = createStore<
-    UseMutationResult<TData, TError, TVariables, TOnMutateResult>
-  >({
-    ...observer.getCurrentResult(),
-    mutate,
-    mutateAsync: observer.getCurrentResult().mutate,
-  })
+  const initialResult = observer.getCurrentResult()
+  const [state, setState] = createStore<
+    UseMutationResult<TData, TError, TVariables, TOnMutateResult>
+  >({
+    ...initialResult,
+    mutate,
+    mutateAsync: initialResult.mutate,
+  })
packages/solid-query/src/types.ts (1)

147-151: Add JSDoc for TOnMutateResult on SolidMutationOptions
Document that the TOnMutateResult type parameter represents the return value of the onMutate callback.

packages/svelte-query/src/types.ts (1)

96-105: Minor: tighten readability by naming the parameters type.

Using Parameters<...> inline is fine, but a local alias improves readability and compiler diagnostics.

 export type CreateMutateFunction<
   TData = unknown,
   TError = DefaultError,
   TVariables = void,
   TOnMutateResult = unknown,
 > = (
-  ...args: Parameters<
-    MutateFunction<TData, TError, TVariables, TOnMutateResult>
-  >
+  ...args: MutateArgs<TData, TError, TVariables, TOnMutateResult>
 ) => void
+
+export type MutateArgs<
+  TData = unknown,
+  TError = DefaultError,
+  TVariables = void,
+  TOnMutateResult = unknown,
+> = Parameters<MutateFunction<TData, TError, TVariables, TOnMutateResult>>
packages/vue-query/src/useMutation.ts (1)

61-72: Avoid exposing/overwriting mutate/reset via toRefs; omit them before toRefs

Currently toRefs(readonlyState) includes mutate/reset, which you then overwrite. Cleanly omit first to match MutationResult and drop the unsafe cast.

Apply:

-  const resultRefs = toRefs(readonlyState) as ToRefs<
-    Readonly<MutationResult<TData, TError, TVariables, TOnMutateResult>>
-  >
+  const { mutate: _m, reset: _r, ...rest } = readonlyState as any
+  const resultRefs = toRefs(rest) as ToRefs<
+    Readonly<MutationResult<TData, TError, TVariables, TOnMutateResult>>
+  >

Also applies to: 127-131

packages/angular-query-experimental/src/__tests__/mutation-options.test-d.ts (1)

117-142: Optional: add a test where onMutate returns void to assert onMutateResult is undefined

Covers the negative path and mirrors React/Svelte suites.

Proposed addition:

it('onMutate returning void yields undefined onMutateResult', () => {
  mutationOptions<number>({
    mutationFn: (id: string) => Promise.resolve(id.length),
    onMutate: () => {},
    onSuccess: (_d, _v, onMutateResult) => {
      expectTypeOf(onMutateResult).toEqualTypeOf<undefined>()
    },
  })
})
docs/framework/angular/guides/optimistic-updates.md (1)

65-71: Optional: mirror the earlier pending-state pattern

For consistency with the first snippet, consider returning the Promise in this earlier onSettled too.

-  onSettled: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
+  onSettled: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),

(If you want, make it async + return as in the other examples.)

packages/query-core/src/__tests__/mutations.test.tsx (1)

796-796: Remove stray console.log in tests

Avoids noisy test output.

-      console.log(results)
packages/query-core/src/mutationObserver.ts (2)

166-166: Rename local “scope” to “onMutateResult” for clarity with MutateOptions signature.

Internally we store the value as scope, but MutateOptions’ 3rd parameter is named onMutateResult. Renaming here reduces cognitive overhead.

-        const scope = this.#currentResult.scope
+        const onMutateResult = this.#currentResult.scope
@@
-          this.#mutateOptions.onSuccess?.(
-            action.data,
-            variables,
-            scope,
-            context,
-          )
+          this.#mutateOptions.onSuccess?.(
+            action.data,
+            variables,
+            onMutateResult,
+            context,
+          )
@@
-          this.#mutateOptions.onSettled?.(
+          this.#mutateOptions.onSettled?.(
             action.data,
             null,
             variables,
-            scope,
+            onMutateResult,
             context,
           )
@@
-          this.#mutateOptions.onError?.(action.error, variables, scope, context)
+          this.#mutateOptions.onError?.(
+            action.error,
+            variables,
+            onMutateResult,
+            context,
+          )
@@
           this.#mutateOptions.onSettled?.(
             undefined,
             action.error,
             variables,
-            scope,
+            onMutateResult,
             context,
           )

Also applies to: 175-187, 189-195


164-165: Consider invoking mutateOptions callbacks regardless of subscription presence.

Current guard requires hasListeners(); this can suppress onSuccess/onError/onSettled when using the observer imperatively without subscribers.

If this wasn’t intentional, change:

-      if (this.#mutateOptions && this.hasListeners()) {
+      if (this.#mutateOptions) {

Would you like a follow-up PR to add a test that asserts callbacks fire with zero listeners?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cb89bfe and 60d0300.

📒 Files selected for processing (45)
  • docs/framework/angular/guides/mutations.md (4 hunks)
  • docs/framework/angular/guides/optimistic-updates.md (3 hunks)
  • docs/framework/angular/reference/functions/injectmutation.md (3 hunks)
  • docs/framework/angular/reference/functions/mutationoptions.md (2 hunks)
  • docs/framework/angular/reference/interfaces/basemutationnarrowing.md (6 hunks)
  • docs/framework/angular/reference/interfaces/createmutationoptions.md (2 hunks)
  • docs/framework/angular/reference/type-aliases/createbasemutationresult.md (2 hunks)
  • docs/framework/angular/reference/type-aliases/createmutateasyncfunction.md (2 hunks)
  • docs/framework/angular/reference/type-aliases/createmutatefunction.md (2 hunks)
  • docs/framework/angular/reference/type-aliases/createmutationresult.md (2 hunks)
  • docs/framework/react/guides/mutations.md (4 hunks)
  • docs/framework/react/guides/optimistic-updates.md (3 hunks)
  • docs/framework/react/reference/useMutation.md (3 hunks)
  • docs/framework/svelte/reference/functions/createmutation.md (2 hunks)
  • docs/framework/svelte/reference/type-aliases/createbasemutationresult.md (2 hunks)
  • docs/framework/svelte/reference/type-aliases/createmutateasyncfunction.md (2 hunks)
  • docs/framework/svelte/reference/type-aliases/createmutatefunction.md (2 hunks)
  • docs/framework/svelte/reference/type-aliases/createmutationoptions.md (2 hunks)
  • docs/framework/svelte/reference/type-aliases/createmutationresult.md (2 hunks)
  • docs/reference/MutationCache.md (2 hunks)
  • examples/react/optimistic-updates-cache/src/pages/index.tsx (2 hunks)
  • packages/angular-query-experimental/src/__tests__/mutation-options.test-d.ts (2 hunks)
  • packages/angular-query-experimental/src/inject-mutation.ts (4 hunks)
  • packages/angular-query-experimental/src/mutation-options.ts (2 hunks)
  • packages/angular-query-experimental/src/types.ts (7 hunks)
  • packages/eslint-plugin-query/src/__tests__/mutation-property-order.rule.test.ts (2 hunks)
  • packages/query-core/src/__tests__/mutationCache.test.tsx (5 hunks)
  • packages/query-core/src/__tests__/mutations.test.tsx (17 hunks)
  • packages/query-core/src/mutation.ts (12 hunks)
  • packages/query-core/src/mutationCache.ts (4 hunks)
  • packages/query-core/src/mutationObserver.ts (8 hunks)
  • packages/query-core/src/queryClient.ts (1 hunks)
  • packages/query-core/src/types.ts (8 hunks)
  • packages/query-core/src/utils.ts (2 hunks)
  • packages/react-query/src/__tests__/mutationOptions.test-d.tsx (2 hunks)
  • packages/react-query/src/mutationOptions.ts (1 hunks)
  • packages/react-query/src/types.ts (1 hunks)
  • packages/react-query/src/useMutation.ts (2 hunks)
  • packages/solid-query/src/types.ts (1 hunks)
  • packages/solid-query/src/useMutation.ts (1 hunks)
  • packages/svelte-query/src/createMutation.ts (1 hunks)
  • packages/svelte-query/src/types.ts (1 hunks)
  • packages/vue-query/src/mutationCache.ts (1 hunks)
  • packages/vue-query/src/queryClient.ts (1 hunks)
  • packages/vue-query/src/useMutation.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (18)
  • docs/framework/angular/reference/functions/mutationoptions.md
  • docs/framework/angular/reference/type-aliases/createmutateasyncfunction.md
  • docs/framework/react/guides/optimistic-updates.md
  • docs/framework/svelte/reference/type-aliases/createbasemutationresult.md
  • packages/react-query/src/mutationOptions.ts
  • docs/framework/angular/reference/interfaces/createmutationoptions.md
  • packages/vue-query/src/mutationCache.ts
  • packages/query-core/src/utils.ts
  • packages/query-core/src/queryClient.ts
  • packages/query-core/src/tests/mutationCache.test.tsx
  • docs/framework/svelte/reference/type-aliases/createmutationoptions.md
  • packages/react-query/src/useMutation.ts
  • docs/framework/angular/reference/type-aliases/createmutationresult.md
  • examples/react/optimistic-updates-cache/src/pages/index.tsx
  • docs/framework/svelte/reference/type-aliases/createmutateasyncfunction.md
  • packages/query-core/src/mutation.ts
  • docs/framework/angular/reference/type-aliases/createmutatefunction.md
  • packages/query-core/src/types.ts
🧰 Additional context used
🧬 Code graph analysis (13)
packages/svelte-query/src/types.ts (2)
packages/query-core/src/types.ts (6)
  • OmitKeyof (19-29)
  • MutationObserverOptions (1145-1152)
  • DefaultError (47-51)
  • MutateFunction (1181-1189)
  • Override (31-35)
  • MutationObserverResult (1340-1349)
packages/angular-query-experimental/src/types.ts (4)
  • CreateMutateFunction (175-184)
  • CreateMutateAsyncFunction (189-194)
  • CreateBaseMutationResult (199-214)
  • CreateMutationResult (314-327)
packages/vue-query/src/queryClient.ts (2)
packages/vue-query/src/types.ts (1)
  • MaybeRefDeep (29-37)
packages/query-core/src/types.ts (1)
  • MutationObserverOptions (1145-1152)
packages/svelte-query/src/createMutation.ts (3)
packages/svelte-query/src/types.ts (4)
  • StoreOrVal (21-21)
  • CreateMutationOptions (86-94)
  • CreateMutationResult (132-139)
  • CreateMutateFunction (96-105)
packages/query-core/src/queryClient.ts (1)
  • QueryClient (61-648)
packages/query-core/src/mutationObserver.ts (2)
  • MutationObserver (23-206)
  • mutate (128-143)
packages/solid-query/src/useMutation.ts (3)
packages/react-query/src/types.ts (3)
  • UseMutationOptions (192-200)
  • UseMutationResult (237-242)
  • UseMutateFunction (202-211)
packages/solid-query/src/types.ts (3)
  • UseMutationOptions (153-158)
  • UseMutationResult (195-200)
  • UseMutateFunction (160-169)
packages/query-core/src/mutationObserver.ts (3)
  • MutationObserver (23-206)
  • mutate (128-143)
  • state (145-159)
packages/react-query/src/types.ts (2)
packages/query-core/src/types.ts (5)
  • MutationObserverOptions (1145-1152)
  • DefaultError (47-51)
  • MutateFunction (1181-1189)
  • Override (31-35)
  • MutationObserverResult (1340-1349)
packages/solid-query/src/types.ts (2)
  • UseMutateFunction (160-169)
  • UseMutateAsyncFunction (171-176)
packages/query-core/src/mutationCache.ts (3)
packages/query-core/src/mutation.ts (2)
  • Mutation (88-389)
  • MutationState (26-45)
packages/query-core/src/types.ts (3)
  • MutationFunctionContext (1094-1098)
  • DefaultError (47-51)
  • MutationOptions (1105-1143)
packages/query-core/src/utils.ts (2)
  • MutationFilters (46-70)
  • matchMutation (175-202)
packages/angular-query-experimental/src/inject-mutation.ts (2)
packages/angular-query-experimental/src/types.ts (3)
  • CreateMutationOptions (162-170)
  • CreateMutationResult (314-327)
  • CreateMutateFunction (175-184)
packages/query-core/src/mutationObserver.ts (1)
  • MutationObserver (23-206)
packages/angular-query-experimental/src/types.ts (2)
packages/query-core/src/types.ts (5)
  • OmitKeyof (19-29)
  • MutationObserverOptions (1145-1152)
  • MutateFunction (1181-1189)
  • Override (31-35)
  • MutationObserverResult (1340-1349)
packages/svelte-query/src/types.ts (4)
  • CreateMutateFunction (96-105)
  • CreateMutateAsyncFunction (107-112)
  • CreateBaseMutationResult (114-129)
  • CreateMutationResult (132-139)
packages/angular-query-experimental/src/__tests__/mutation-options.test-d.ts (2)
packages/react-query/src/mutationOptions.ts (1)
  • mutationOptions (32-41)
packages/query-core/src/types.ts (2)
  • DefaultError (47-51)
  • MutationFunctionContext (1094-1098)
packages/query-core/src/mutationObserver.ts (3)
packages/query-core/src/types.ts (4)
  • MutationObserverResult (1340-1349)
  • MutationObserverOptions (1145-1152)
  • MutateOptions (1154-1179)
  • MutationFunctionContext (1094-1098)
packages/query-core/src/subscribable.ts (1)
  • Subscribable (1-30)
packages/query-core/src/mutation.ts (4)
  • Mutation (88-389)
  • action (319-388)
  • Action (78-84)
  • getDefaultState (391-409)
packages/solid-query/src/types.ts (3)
packages/query-core/src/types.ts (6)
  • OmitKeyof (19-29)
  • MutationObserverOptions (1145-1152)
  • DefaultError (47-51)
  • MutateFunction (1181-1189)
  • Override (31-35)
  • MutationObserverResult (1340-1349)
packages/react-query/src/types.ts (5)
  • UseMutationOptions (192-200)
  • UseMutateFunction (202-211)
  • UseMutateAsyncFunction (213-218)
  • UseBaseMutationResult (220-235)
  • UseMutationResult (237-242)
packages/vue-query/src/useMutation.ts (1)
  • UseMutationOptions (37-44)
packages/react-query/src/__tests__/mutationOptions.test-d.tsx (2)
packages/react-query/src/mutationOptions.ts (1)
  • mutationOptions (32-41)
packages/query-core/src/types.ts (2)
  • DefaultError (47-51)
  • MutationFunctionContext (1094-1098)
packages/vue-query/src/useMutation.ts (3)
packages/query-core/src/types.ts (6)
  • DistributiveOmit (14-17)
  • MutationObserverResult (1340-1349)
  • MutationObserverOptions (1145-1152)
  • DefaultError (47-51)
  • MutateFunction (1181-1189)
  • MutateOptions (1154-1179)
packages/vue-query/src/types.ts (2)
  • ShallowOption (58-63)
  • MaybeRefDeep (29-37)
packages/react-query/src/useMutation.ts (1)
  • useMutation (19-69)
🔇 Additional comments (47)
packages/eslint-plugin-query/src/__tests__/mutation-property-order.rule.test.ts (2)

148-149: LGTM: onSettled signature and logging align with rename.


205-210: LGTM: regression test updated to onMutateResult consistently.

packages/solid-query/src/useMutation.ts (3)

19-24: Rename to TOnMutateResult is consistent and non-breaking for consumers.

Types align with query-core and other bindings. Good change.


33-39: Mutate function typing mirrors core signature via Parameters<>.

Keeps mutate’s arg types in sync with core. Looks good.


26-31: Approve: Observer generics are correctly wired and no leftover TContext references remain.

packages/solid-query/src/types.ts (5)

153-159: UseMutationOptions updated to Accessor<SolidMutationOptions<…, TOnMutateResult>>.

Matches Solid’s Accessor pattern; OK.


160-169: UseMutateFunction switched to MutateFunction<…, TOnMutateResult> via Parameters<>.

Keeps callsite types aligned with core; good.


171-177: UseMutateAsyncFunction alias updated.

Promise-returning mutateAsync correctly mirrors core’s MutateFunction.


178-193: UseBaseMutationResult override and extras (mutate/mutateAsync) updated.

Override shape matches React/Vue counterparts; consistent.


195-201: UseMutationResult passthrough updated.

End-to-end generic flow is consistent.

packages/svelte-query/src/types.ts (4)

107-113: Async mutate alias aligns with core.

CreateMutateAsyncFunction correctly mirrors MutateFunction with TOnMutateResult. LGTM.


114-129: Result typing override is consistent.

Overriding mutate to the void-returning version and exposing mutateAsync separately matches existing framework conventions. LGTM.


131-139: Readable result threading TOnMutateResult is correct.

The store wraps CreateBaseMutationResult<..., TOnMutateResult> as expected. LGTM.


85-94: Approve TOnMutateResult rename; no leftover TContext references
All uses of “context” now refer solely to Svelte’s context API, and no TContext type remains.

packages/svelte-query/src/createMutation.ts (3)

17-24: Generic rename is correctly threaded through the public API.

createMutation now exposes TOnMutateResult across options and result. LGTM.


28-35: Observer instantiation and local mutate type propagation are consistent.

Generics on MutationObserver and CreateMutateFunction match core. LGTM.


47-51: Alias mutateAsync from core result: OK.

Mapping mutateAsync: $result.mutate preserves the Promise-returning API while mutate stays fire-and-forget. LGTM.

packages/vue-query/src/queryClient.ts (1)

459-465: Type rename to TOnMutateResult in setMutationDefaults — LGTM

Signature aligns with core MutationObserverOptions generics; no runtime impact.

docs/framework/svelte/reference/type-aliases/createmutatefunction.md (1)

6-10: Rename to TOnMutateResult — LGTM

Docs and signature are consistent with the updated generics across the codebase.

Also applies to: 22-29

docs/framework/svelte/reference/type-aliases/createmutationresult.md (1)

6-11: Rename to TOnMutateResult — LGTM

Matches CreateBaseMutationResult usage and default type params.

Also applies to: 23-23

packages/react-query/src/__tests__/mutationOptions.test-d.tsx (2)

58-71: onMutateResult inference path verified — LGTM

The test correctly asserts onMutateResult flow and optionality.


73-91: Confirm callback argument order matches core types
Verify that the onSuccess/onError/onSettled callbacks here use the exact parameter order and arity defined in MutateOptions (in packages/query-core/src/types.ts)—the context returned by onMutate should be the final argument.

docs/framework/angular/reference/type-aliases/createbasemutationresult.md (1)

6-17: Angular docs updated to TOnMutateResult — LGTM

All dependent type references (Override, mutate/mutateAsync) are consistent.

Also applies to: 24-29, 40-40

packages/react-query/src/types.ts (1)

191-243: React mutation typings migrated to TOnMutateResult — LGTM

UseMutationOptions/Result and mutate function aliases correctly propagate the new generic.

docs/framework/svelte/reference/functions/createmutation.md (1)

9-13: Rename to TOnMutateResult — LGTM

Signature, options, and return types are aligned with the new generic.

Also applies to: 23-23, 29-29, 37-37

docs/framework/angular/reference/interfaces/basemutationnarrowing.md (1)

6-17: Interface generic rename to TOnMutateResult — LGTM

Narrowing helpers and mutate signatures consistently reflect the new generic.

packages/vue-query/src/useMutation.ts (2)

27-35: Rename to TOnMutateResult looks consistent with core types

The aliasing to MutationObserverResult with TOnMutateResult aligns with query-core generics.


78-85: API surface and mutate signature updated correctly

Generic defaults and MutateOptions wiring match query-core. Runtime behavior remains intact.

Also applies to: 106-113

packages/angular-query-experimental/src/types.ts (3)

162-171: CreateMutationOptions: generic rename is correct and preserves compatibility with core options

Extends MutationObserverOptions with TOnMutateResult as in core.


175-185: Mutate function/async types aligned with core MutateFunction

Signatures and parameter forwarding via Parameters<> are spot on.

Also applies to: 189-195


199-214: Result and narrowing types propagate TOnMutateResult consistently

Type guards and status-based extraction remain correct with the new generic.

Also applies to: 219-229, 235-309, 314-327

packages/angular-query-experimental/src/__tests__/mutation-options.test-d.ts (3)

62-75: onMutateResult inference test looks good

Return value flows to onSuccess as expected.


77-96: Context typing coverage is solid

All callbacks receive MutationFunctionContext and assertions are correct.


144-157: Downstream type inference check is helpful

Keeps CreateMutationResult generic flow honest across injectMutation.

docs/framework/angular/reference/functions/injectmutation.md (1)

8-13: Docs reflect TOnMutateResult rename correctly

Signature and return type are in sync with code.

Also applies to: 45-46

docs/framework/angular/guides/optimistic-updates.md (2)

160-166: Keep “context” param name; “scope” refers to onMutateResult on state

Context here is the MutationFunctionContext (has client/meta). Do not rename to scope.

If desired, add an example that references state.scope elsewhere to demonstrate the distinction.


125-139: Guard single-item setQueryData and clarify returned result naming

Add undefined guard; keep comment concise.

-    const previousTodo = context.client.getQueryData(['todos', newTodo.id])
+    const previousTodo = context.client.getQueryData(['todos', newTodo.id])
@@
-    context.client.setQueryData(['todos', newTodo.id], newTodo)
+    context.client.setQueryData(['todos', newTodo.id], newTodo)
@@
-    // Return a result with the previous and new todo
+    // Return onMutateResult with the previous and new todo

Likely an incorrect or invalid review comment.

packages/angular-query-experimental/src/inject-mutation.ts (3)

45-59: Public API generic rename applied correctly

injectMutation now exposes TOnMutateResult; options and return type mirror that.


72-83: Observer instance typing updated and stable

Local instance memoization prevents re-instantiation; generics match core.


85-93: Signal wiring and error propagation LGTM

Mutate wrapper, options updates, subscription, and re-exposed mutate/mutateAsync align with core behavior.

Also applies to: 105-111, 126-155, 157-168, 170-176

packages/angular-query-experimental/src/mutation-options.ts (2)

38-47: Type rename to TOnMutateResult looks correct

Overload 1 is updated consistently for params and return type. No runtime impact.


55-61: Consistent TOnMutateResult propagation across overload 2 and impl

Signatures align with core types; defaults remain intact.

Also applies to: 97-101

docs/reference/MutationCache.md (1)

31-44: Global callback signatures align with core changes

Added onMutateResult and MutationFunctionContext parameters read correctly.

packages/query-core/src/__tests__/mutations.test.tsx (1)

56-61: Good: verifies mutationFn receives MutationFunctionContext

Covers client, meta, and mutationKey, matching the new signature.

packages/query-core/src/mutationCache.ts (1)

18-46: Don’t narrow onMutate return to void. onMutate’s return value is passed as the context to onError and onSettled (e.g. for rollbacks); changing it to void | Promise would break that contract.

Likely an incorrect or invalid review comment.

packages/query-core/src/mutationObserver.ts (2)

9-12: Import of MutationFunctionContext is appropriate.

Matches its use in #notify; no concerns.


17-19: TOnMutateResult type plumbed through consistently.

Listener, class generics, constructor/setOptions, result getter, mutate signature, and default state are aligned. Good API coherence.

Also applies to: 27-42, 45-51, 66-75, 102-104, 114-115, 130-131, 148-149, 158-159

@joshuaellis
Copy link
Author

polite bump @TkDodo – left a comment around mutationState and whether we should keep the name scope.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants