Skip to content

docs(useMutationState): TMutation safety warning is missing from hook JSDoc — only visible on type definition #10792

@n-satoshi061

Description

@n-satoshi061

Summary

The TMutation generic added to useMutationState (and its equivalents across all adapters) includes a safety warning, but the warning only appears on the MutationStateOptions type definition — not on the hook function itself. In practice, users hover over the function, not the type, so the warning is effectively invisible in most IDEs.

Affected version

Introduced in the TMutation generic PR targeting v5.x (current: v5.100.14). Not present in v4 (no useMutationState).

Affected packages

All adapters updated in the TMutation PR:

  • @tanstack/react-query
  • @tanstack/vue-query
  • @tanstack/solid-query
  • @tanstack/svelte-query
  • @tanstack/preact-query
  • @tanstack/lit-query

Current behavior

The warning lives only on the type parameter of MutationStateOptions:

// ✅ Warning is here — but users rarely hover over a type definition
type MutationStateOptions<
  TResult = MutationState,
  /**
   * Narrows the type of the `mutation` argument passed to `select`.
   * This is a caller-side assertion — the mutation cache stores mutations as
   * the base `Mutation` type, so it is the caller's responsibility to ensure
   * `TMutation` matches the actual mutations in the cache (e.g. by specifying
   * a `mutationKey` in `filters`).
   */
  TMutation extends Mutation<any, any, any, any> = Mutation,
> = { ... }

// ❌ No warning here — but this is where users look
export function useMutationState<
  TResult = MutationState,
  TMutation extends Mutation<any, any, any, any> = Mutation,
>(options: MutationStateOptions<TResult, TMutation> = {}, ...): Array<TResult>

Why this matters

The as unknown as TMutation cast inside getResult() completely bypasses TypeScript's structural type checker at runtime. There is no runtime guard that validates the actual cache entry matches TMutation. If a user specifies TMutation without using filters to narrow the cache to matching mutations, TypeScript will silently accept incorrect code:

// TypeScript: ✅  Runtime: 💥 (cache may hold Mutation<OrderData>, not Mutation<UserData>)
const states = useMutationState<MutationState<UserData>, Mutation<UserData>>({
  // ⚠️ No mutationKey filter — any mutation in the cache will be cast to Mutation<UserData>
  select: (m) => m.state.data, // typed as UserData | undefined, but could be anything
})

The feature is a deliberate caller-side assertion (similar to as casts), which is a reasonable design. The problem is that the responsibility is not communicated at the call site.

Expected behavior

The useMutationState function (and equivalents in all adapters) should have a JSDoc comment on the TMutation type parameter explaining:

  1. This is a caller-side assertion, not a runtime-verified narrowing.
  2. TypeScript will not catch type mismatches between TMutation and the actual cache contents.
  3. Users should always pair TMutation with a filters.mutationKey (or equivalent) to ensure only matching mutations are selected.

Suggested fix

Add a JSDoc comment on the TMutation parameter of the exported hook function, mirroring the one already on MutationStateOptions:

export function useMutationState<
  TResult = MutationState,
  /**
   * Narrows the type of the `mutation` argument passed to `select`.
   * **This is a caller-side assertion with no runtime validation.**
   * The mutation cache stores all mutations as the base `Mutation` type,
   * so TypeScript will not catch a mismatch between `TMutation` and the
   * actual mutations in the cache.
   * Always use `filters` (e.g. `mutationKey`) to ensure only mutations of
   * the expected type are matched.
   */
  TMutation extends Mutation<any, any, any, any> = Mutation,
>(options: MutationStateOptions<TResult, TMutation> = {}, ...): Array<TResult>

The same change should be applied to the equivalent function in all six adapters.

Additional context

  • This was identified during review of the TMutation generic PR.
  • The same as unknown as TMutation cast exists identically in react-query, vue-query, solid-query, svelte-query, preact-query, and lit-query — so the fix is mechanical and low-risk once the right wording is agreed on.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions