Skip to content

fix(types): propagate generic type params to useMutationState select callback#10373

Open
Zelys-DFKH wants to merge 3 commits intoTanStack:mainfrom
Zelys-DFKH:fix/use-mutation-state-generics
Open

fix(types): propagate generic type params to useMutationState select callback#10373
Zelys-DFKH wants to merge 3 commits intoTanStack:mainfrom
Zelys-DFKH:fix/use-mutation-state-generics

Conversation

@Zelys-DFKH
Copy link
Copy Markdown
Contributor

@Zelys-DFKH Zelys-DFKH commented Apr 1, 2026

🎯 Changes

Closes #9825.

When you pass a typed MutationState<TData, TError, TVariables> as TResult, the select callback now receives Mutation<TData, TError, TVariables, TContext> instead of the base Mutation type. Before this change you had to cast manually:

// before
useMutationState<MutationState<MyData, MyError, MyVars>>({
  select: (mutation) =>
    (mutation as Mutation<MyData, MyError, MyVars>).state.data,
})

// after
useMutationState<MutationState<MyData, MyError, MyVars>>({
  select: (mutation) => mutation.state.data, // mutation is correctly typed
})

How it works

MutationStateOptions gains a second type param TMutation that defaults to MutationTypeFromResult<TResult>:

type MutationTypeFromResult<TResult> = [TResult] extends [
  MutationState<infer TData, infer TError, infer TVariables, infer TOnMutateResult>
]
  ? Mutation<TData, TError, TVariables, TOnMutateResult>
  : Mutation

The tuple wrapper [TResult] extends [...] makes the conditional non-distributive, which stops TypeScript from producing a union when TResult is still unresolved. The second param keeps backward compatibility: callers that do not provide TResult at all get the same Mutation type they always did.

Applied across all five adapters: react, preact, solid, vue, and svelte.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • Bug Fixes
    • Fixed generic type parameter propagation in useMutationState across React, Preact, Solid, Vue, and Svelte query packages, ensuring the select callback receives correctly typed mutation objects for improved type safety and inference.

…callback

When TResult is a typed MutationState, the select callback parameter
now receives the correctly typed Mutation instead of the base Mutation type.

Adds a second type param TMutation (defaulting to MutationTypeFromResult<TResult>)
to MutationStateOptions across all five framework adapters. Uses a non-distributive
conditional type to avoid union expansion when TResult is unresolved.

Fixes TanStack#9825
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 1, 2026

📝 Walkthrough

Walkthrough

This PR fixes type inference for the useMutationState hook across five framework packages by propagating generic type parameters into the select callback. A conditional MutationTypeFromResult type is introduced to derive the correct Mutation shape from the provided TResult generic parameter, enabling proper type narrowing in select functions.

Changes

Cohort / File(s) Summary
Release Announcement
.changeset/fix-mutation-state-generics.md
Announces patch releases for five TanStack Query packages with a types fix for generic propagation in useMutationState select callback.
Hook Implementations
packages/react-query/src/useMutationState.ts, packages/preact-query/src/useMutationState.ts, packages/solid-query/src/useMutationState.ts, packages/vue-query/src/useMutationState.ts, packages/svelte-query/src/useMutationState.*.ts, packages/svelte-query/src/types.ts
Introduces MutationTypeFromResult conditional type and adds TMutation generic parameter to MutationStateOptions. Updates select callback signature from (mutation: Mutation) => TResult to (mutation: TMutation) => TResult across all framework adapters, with type casts in implementations to maintain runtime compatibility.
Type Test Cases
packages/react-query/src/__tests__/useMutationState.test-d.tsx, packages/preact-query/src/__tests__/useMutationState.test-d.tsx, packages/solid-query/src/__tests__/useMutationState.test-d.tsx
Adds type-level test cases verifying that generic type parameters are correctly propagated to the select callback parameter and that Mutation type is narrowed according to the TResult generic.
Example Component
packages/svelte-query/tests/useMutationState/SelectExample.svelte
Updates mutationStateOpts prop type annotation to reflect the expanded MutationStateOptions generic arity (now accepting both TResult and TMutation).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 wiggles nose at types
Generics now flow through select, hooray!
TMutation springs from TResult's sight,
Five frameworks dance in TypeScript delight! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and specifically describes the primary change: fixing type propagation for generic type parameters in the useMutationState select callback.
Description check ✅ Passed The PR description comprehensively covers the changes, motivation, and implementation details, with clear before/after examples and technical explanations, fulfilling all required template sections.
Linked Issues check ✅ Passed The PR successfully addresses issue #9825 by implementing generic type propagation to the useMutationState select callback across all five framework adapters using the MutationTypeFromResult conditional type.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the stated objective of propagating generic type parameters to useMutationState select callbacks; no out-of-scope modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@nx-cloud
Copy link
Copy Markdown

nx-cloud bot commented Apr 1, 2026

View your CI Pipeline Execution ↗ for commit 57bb423

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

☁️ Nx Cloud last updated this comment at 2026-04-01 17:48:35 UTC

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 1, 2026

More templates

@tanstack/angular-query-experimental

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

@tanstack/eslint-plugin-query

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

@tanstack/preact-query

npm i https://pkg.pr.new/@tanstack/preact-query@10373

@tanstack/preact-query-devtools

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

@tanstack/preact-query-persist-client

npm i https://pkg.pr.new/@tanstack/preact-query-persist-client@10373

@tanstack/query-async-storage-persister

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

@tanstack/query-broadcast-client-experimental

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

@tanstack/query-core

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

@tanstack/query-devtools

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

@tanstack/query-persist-client-core

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

@tanstack/query-sync-storage-persister

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

@tanstack/react-query

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

@tanstack/react-query-devtools

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

@tanstack/react-query-next-experimental

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

@tanstack/react-query-persist-client

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

@tanstack/solid-query

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

@tanstack/solid-query-devtools

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

@tanstack/solid-query-persist-client

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

@tanstack/svelte-query

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

@tanstack/svelte-query-devtools

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

@tanstack/svelte-query-persist-client

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

@tanstack/vue-query

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

@tanstack/vue-query-devtools

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

commit: 57bb423

Copy link
Copy Markdown
Contributor

@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

🧹 Nitpick comments (1)
packages/svelte-query/src/useMutationState.svelte.ts (1)

34-38: Simplify callback cast by casting the mutation value instead.

The current double-cast at line 35 obscures that options.select expects TMutation (per the type definition). Casting mutation as TMutation at the call site is clearer and type-safe.

Suggested refactor
-        (options.select
-          ? (options.select as unknown as (mutation: Mutation) => TResult)(
-              mutation,
-            )
-          : mutation.state) as TResult,
+        (options.select
+          ? options.select(mutation as TMutation)
+          : mutation.state) as TResult,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/svelte-query/src/useMutationState.svelte.ts` around lines 34 - 38,
The double-cast of options.select is confusing; instead cast the mutation value
to the expected TMutation and call the select callback directly. Update the
conditional expression in useMutationState (where options.select is invoked) to
call options.select(mutation as TMutation) and keep the fallback to
mutation.state, ensuring the overall expression is still cast to TResult;
reference the symbols options.select, mutation, TMutation, TResult, and
mutation.state when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/preact-query/src/__tests__/useMutationState.test-d.tsx`:
- Around line 1-8: ESLint import/order wants the local module import before
external package type imports; reorder the imports so the relative import of
useMutationState comes before the type-only import from `@tanstack/query-core`
(i.e., move "import { useMutationState } from '../useMutationState'" above the
"import type { Mutation, MutationState, MutationStatus } from
'@tanstack/query-core'").

---

Nitpick comments:
In `@packages/svelte-query/src/useMutationState.svelte.ts`:
- Around line 34-38: The double-cast of options.select is confusing; instead
cast the mutation value to the expected TMutation and call the select callback
directly. Update the conditional expression in useMutationState (where
options.select is invoked) to call options.select(mutation as TMutation) and
keep the fallback to mutation.state, ensuring the overall expression is still
cast to TResult; reference the symbols options.select, mutation, TMutation,
TResult, and mutation.state when making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b193f4a2-1fbb-40ac-be61-462d3ccab642

📥 Commits

Reviewing files that changed from the base of the PR and between 67b12ae and b73c36a.

📒 Files selected for processing (11)
  • .changeset/fix-mutation-state-generics.md
  • packages/preact-query/src/__tests__/useMutationState.test-d.tsx
  • packages/preact-query/src/useMutationState.ts
  • packages/react-query/src/__tests__/useMutationState.test-d.tsx
  • packages/react-query/src/useMutationState.ts
  • packages/solid-query/src/__tests__/useMutationState.test-d.tsx
  • packages/solid-query/src/useMutationState.ts
  • packages/svelte-query/src/types.ts
  • packages/svelte-query/src/useMutationState.svelte.ts
  • packages/svelte-query/tests/useMutationState/SelectExample.svelte
  • packages/vue-query/src/useMutationState.ts

Comment on lines +1 to 8
import type {
Mutation,
MutationState,
MutationStatus,
} from '@tanstack/query-core'
import { describe, expectTypeOf, it } from 'vitest'

import { useMutationState } from '../useMutationState'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix import order to satisfy ESLint.

import/order currently expects the @tanstack/query-core type import to come after ../useMutationState.

Suggested fix
-import type {
-  Mutation,
-  MutationState,
-  MutationStatus,
-} from '@tanstack/query-core'
 import { describe, expectTypeOf, it } from 'vitest'
 
 import { useMutationState } from '../useMutationState'
+import type {
+  Mutation,
+  MutationState,
+  MutationStatus,
+} from '@tanstack/query-core'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import type {
Mutation,
MutationState,
MutationStatus,
} from '@tanstack/query-core'
import { describe, expectTypeOf, it } from 'vitest'
import { useMutationState } from '../useMutationState'
import { describe, expectTypeOf, it } from 'vitest'
import { useMutationState } from '../useMutationState'
import type {
Mutation,
MutationState,
MutationStatus,
} from '@tanstack/query-core'
🧰 Tools
🪛 ESLint

[error] 1-5: @tanstack/query-core type import should occur after import of ../useMutationState

(import/order)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/preact-query/src/__tests__/useMutationState.test-d.tsx` around lines
1 - 8, ESLint import/order wants the local module import before external package
type imports; reorder the imports so the relative import of useMutationState
comes before the type-only import from `@tanstack/query-core` (i.e., move "import
{ useMutationState } from '../useMutationState'" above the "import type {
Mutation, MutationState, MutationStatus } from '@tanstack/query-core'").

Replace the double-cast `(fn as unknown as (m: Mutation) => TResult)(m)`
with the direct `fn(m as TMutation)` across all five framework packages.

The constraint `TMutation extends Mutation<any, any, any, any>` creates
sufficient overlap for the single cast to compile cleanly on TS 5.4–6.0.
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.

useMutationState does not propagate generics into select callback (type inference lost)

1 participant