Skip to content

fix(vue-query): preserve TQueryKey inference with generic params (#8199)#10584

Open
ousamabenyounes wants to merge 2 commits into
TanStack:mainfrom
ousamabenyounes:fix/issue-8199
Open

fix(vue-query): preserve TQueryKey inference with generic params (#8199)#10584
ousamabenyounes wants to merge 2 commits into
TanStack:mainfrom
ousamabenyounes:fix/issue-8199

Conversation

@ousamabenyounes
Copy link
Copy Markdown
Contributor

@ousamabenyounes ousamabenyounes commented Apr 25, 2026

Summary

Fixes #8199.

UseQueryOptions wraps every property of QueryObserverOptions in MaybeRefDeep<…DeepUnwrapRef<TQueryKey>…>. For the queryKey property that produced an inference-hostile type — the recursive DeepUnwrapRef over a generic TQueryKey is opaque to TypeScript's homomorphic mapped-type inference, so TQueryKey collapsed to its default (QueryKey) whenever useQuery was called inside a composable that propagated a generic into the key.

This PR special-cases the queryKey property in the mapped type so it is wrapped only in a single-level MaybeRef<TQueryKey>. Inference now binds TQueryKey to the supplied key shape, while every other property keeps the existing MaybeRefDeep<…DeepUnwrapRef<TQueryKey>…> mapping (so queryFn's queryKey parameter is still the unwrapped form).

Repro from the issue (now compiles cleanly and types as UseQueryReturnType<'apple' | 'broccoli', Error>):

function useBasket<T extends 'fruit' | 'vegetable'>(type: T) {
  return useQuery({
    queryKey: ['basket', type] as const,
    queryFn({ queryKey: [, type] }) {
      return getBasket(type)
    },
  })
}

The same code already worked in react-query; this aligns vue-query behavior with it.

Verification

  • New type test in useQuery.test-d.ts reproducing the exact code from the issue
  • pnpm exec nx run @tanstack/vue-query:test:types — passes on TS 5.4 → 6.0
  • pnpm test:lib — vue 2 / 2.7 / 3 all 290/290 pass, 0 regressions
  • pnpm exec nx run @tanstack/vue-query:test:eslint — no errors
  • Existing queryKey: [...key, refValue] (refs as elements) and queryKey: ref([...]) (ref-wrapped key) patterns still typecheck
  • Changeset added (patch for @tanstack/vue-query)

Generated by Ora Studio
Vibe coded by ousamabenyounes

Summary by CodeRabbit

  • Bug Fixes

    • Restored correct generic type inference for query operations when wrapped inside generic composables, improving TypeScript type safety with no runtime behavior changes.
  • Tests

    • Added compile-time type tests to validate generic type propagation for wrapped query usages.
  • Chores

    • Added a changeset to publish a patch release documenting the type-inference fix.

Review Change Stack


Vibe Coded by Ousama Ben Younes
Developed With Ora Studio (Claude Code)

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 25, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Modifies vue-query types and adds a type test and changeset: UseQueryOptions now wraps queryKey with MaybeRef<TQueryKey> (instead of MaybeRefDeep<DeepUnwrapRef<TQueryKey>>), preserving generic TQueryKey inference when useQuery is wrapped by a generic composable.

Changes

Preserve TQueryKey inference

Layer / File(s) Summary
Core Type Definition
packages/vue-query/src/useQuery.ts
Update UseQueryOptions mapped type: treat queryKey as MaybeRef<QueryObserverOptions<...>['queryKey']> using TQueryKey directly; other properties remain MaybeRefDeep<DeepUnwrapRef<TQueryKey>>.
Type Tests
packages/vue-query/src/__tests__/useQuery.test-d.ts
Import UseQueryReturnType and add a compile-time test that useBasket<T> (a generic composable wrapping useQuery) preserves the inferred UseQueryReturnType for the generic key values.
Changeset
.changeset/vue-query-generic-querykey-inference.md
Add changeset marking a patch release and noting the fix for TQueryKey inference when useQuery is wrapped by a generic composable.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 I nudge the keys with gentle cheer,
Generics now whisper loud and clear.
No more unknowns in query arrays,
Types prance free through composable ways.
🥕

🚥 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 title clearly and specifically describes the main change: fixing TQueryKey type inference when useQuery is wrapped in a generic composable.
Description check ✅ Passed The description covers the fix, root cause, and verification steps, though it lacks explicit checklist completion marks despite addressing all checklist items.
Linked Issues check ✅ Passed The PR addresses issue #8199 by adjusting TypeScript typings for queryKey inference in generic composables, matching react-query behavior through type-level fixes and comprehensive testing.
Out of Scope Changes check ✅ Passed All changes directly address the linked issue: the type fix in useQuery.ts, the regression test in useQuery.test-d.ts, and the changeset for tracking the patch release.

✏️ 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 25, 2026

View your CI Pipeline Execution ↗ for commit 61b9a18

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

☁️ Nx Cloud last updated this comment at 2026-05-11 08:22:36 UTC

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 25, 2026

More templates

@tanstack/angular-query-experimental

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

@tanstack/eslint-plugin-query

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

@tanstack/lit-query

npm i https://pkg.pr.new/@tanstack/lit-query@10584

@tanstack/preact-query

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

@tanstack/preact-query-devtools

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

@tanstack/preact-query-persist-client

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

@tanstack/query-async-storage-persister

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

@tanstack/query-broadcast-client-experimental

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

@tanstack/query-core

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

@tanstack/query-devtools

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

@tanstack/query-persist-client-core

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

@tanstack/query-sync-storage-persister

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

@tanstack/react-query

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

@tanstack/react-query-devtools

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

@tanstack/react-query-next-experimental

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

@tanstack/react-query-persist-client

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

@tanstack/solid-query

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

@tanstack/solid-query-devtools

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

@tanstack/solid-query-persist-client

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

@tanstack/svelte-query

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

@tanstack/svelte-query-devtools

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

@tanstack/svelte-query-persist-client

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

@tanstack/vue-query

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

@tanstack/vue-query-devtools

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

commit: 61b9a18

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.

🧹 Nitpick comments (1)
packages/vue-query/src/__tests__/useQuery.test-d.ts (1)

155-174: Strengthen the regression assertion.

assertType(useBasket('fruit')) only verifies the expression compiles. The original bug per #8199 was that TQueryKey fell back to unknown/QueryKey and the result was typed as UseQueryReturnType<unknown, Error> — that broader form would also compile here. To lock the regression, assert the propagated TData so a future regression to unknown would fail.

♻️ Proposed stronger assertion
-      assertType(useBasket('fruit'))
+      const query = reactive(useBasket('fruit'))
+      expectTypeOf(query.data).toEqualTypeOf<'apple' | 'broccoli' | undefined>()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vue-query/src/__tests__/useQuery.test-d.ts` around lines 155 - 174,
Replace the weak compile-only assertion assertType(useBasket('fruit')) with a
concrete type assertion that verifies the propagated TData; e.g.
assertType<UseQueryReturnType<'apple', Error>>(useBasket('fruit')). Update the
test to reference useBasket and getBasket so it explicitly expects the data type
'apple' (not unknown), using the UseQueryReturnType generic to lock the
regression.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/vue-query/src/__tests__/useQuery.test-d.ts`:
- Around line 155-174: Replace the weak compile-only assertion
assertType(useBasket('fruit')) with a concrete type assertion that verifies the
propagated TData; e.g. assertType<UseQueryReturnType<'apple',
Error>>(useBasket('fruit')). Update the test to reference useBasket and
getBasket so it explicitly expects the data type 'apple' (not unknown), using
the UseQueryReturnType generic to lock the regression.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 997560b3-0722-4838-94f9-598c1ceb3889

📥 Commits

Reviewing files that changed from the base of the PR and between 441204b and 25f32fb.

📒 Files selected for processing (3)
  • .changeset/vue-query-generic-querykey-inference.md
  • packages/vue-query/src/__tests__/useQuery.test-d.ts
  • packages/vue-query/src/useQuery.ts

@ousamabenyounes
Copy link
Copy Markdown
Contributor Author

Thanks @coderabbitai — addressed the nitpick in beb9b81.

  • Strengthen regression assertion (packages/vue-query/src/__tests__/useQuery.test-d.ts:172): replaced bare assertType(useBasket('fruit')) with assertType<UseQueryReturnType<'apple' | 'broccoli', Error>>(useBasket('fruit')). The bare form only checked the expression compiled, which still held under the old buggy behavior (TQueryKey collapsing to QueryKey, TData inferred as unknown). Pinning the propagated TData locks the regression — any future drift to unknown will now fail the type test.

Verified locally:

  • pnpm exec nx run @tanstack/vue-query:test:types — passes on TS 5.4 → 6.0
  • pnpm exec nx run @tanstack/vue-query:test:lib — 290/290 vitest tests pass (incl. typecheck)
  • pnpm exec nx run @tanstack/vue-query:test:eslint — clean

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 25, 2026

Tip

For best results, initiate chat on the files or code changes.

@ousamabenyounes: That's exactly the right strengthening — pinning TData to 'apple' | 'broccoli' turns the assertion into a genuine regression guard. Under the old behavior where TQueryKey collapsed to QueryKey and TData fell back to unknown, the new form would now correctly fail the type check. Great work on the thorough local verification across TS 5.4–6.0 as well! 🎉

(´• ω •`) ♡
∪ ∪


🧠 Learnings used
Learnt from: ousamabenyounes
Repo: TanStack/query PR: 0
File: :0-0
Timestamp: 2026-04-25T22:35:09.256Z
Learning: In `packages/vue-query/src/useBaseQuery.ts`, the `suspense` method in `UseBaseQueryReturnType` should be declared **outside** the distributive conditional type and typed as `Promise<TResult>` (not pinned to `Promise<QueryObserverResult<TData, TError>>`). This preserves the parameterized observer shape for callers using specialized variants like `DefinedQueryObserverResult` (non-undefined `data`) and `InfiniteQueryObserverResult` (`fetchNextPage`/`hasNextPage`/`data.pages`), and aligns with the runtime value returned by `observer.fetchOptimistic()`.

Learnt from: sukvvon
Repo: TanStack/query PR: 9892
File: packages/solid-query-persist-client/src/__tests__/PersistQueryClientProvider.test.tsx:331-335
Timestamp: 2025-11-22T09:06:05.219Z
Learning: In TanStack/query test files, when a queryFn contains side effects (e.g., setting flags for test verification), prefer async/await syntax for clarity; when there are no side effects, prefer the .then() pattern for conciseness.

Learnt from: Newbie012
Repo: TanStack/query PR: 10295
File: examples/react/eslint-plugin-demo/package.json:8-16
Timestamp: 2026-03-19T09:24:51.614Z
Learning: In TanStack/query, example packages under `examples/` consistently use semver ranges (e.g., `"^5.x.y"`) for TanStack dependencies such as `tanstack/react-query` and `tanstack/eslint-plugin-query` in their `package.json` files, rather than `workspace:*` refs. Do not suggest changing these to workspace refs on the grounds of consistency.

Learnt from: Newbie012
Repo: TanStack/query PR: 10295
File: .changeset/exhaustive-deps-allowlist.md:2-5
Timestamp: 2026-03-19T09:24:23.392Z
Learning: In TanStack/query, breaking behavioral changes to `tanstack/eslint-plugin-query` ESLint rules (e.g., the `exhaustive-deps` rule reporting more granularly) are intentionally versioned as `minor` rather than `major`. The rationale (discussed in https://github.com/TanStack/query/issues/6853#issuecomment-3405204044) is that ESLint rules are opt-in, and escape hatches (such as `allowlist` options) are provided to handle false positives. Do not flag such changeset entries as requiring a major bump.

Learnt from: oscartbeaumont
Repo: TanStack/query PR: 9564
File: packages/solid-query-devtools/src/production.tsx:2-3
Timestamp: 2025-08-19T03:18:18.303Z
Learning: In the solid-query-devtools package, the codebase uses a pattern of type-only default imports combined with typeof for component type annotations (e.g., `import type SolidQueryDevtoolsComp from './devtools'` followed by `typeof SolidQueryDevtoolsComp`). This pattern is consistently used across index.tsx and production.tsx files, and the maintainers prefer consistency over changing this approach.

Learnt from: byungsker
Repo: TanStack/query PR: 10465
File: packages/vue-query/src/__tests__/queryOptions.test-d.ts:292-300
Timestamp: 2026-04-13T15:15:51.168Z
Learning: In `packages/vue-query/src/queryOptions.ts`, the predicate form of `enabled` — i.e., `(query) => boolean` — is not supported by `queryOptions` (throws a TypeScript error). This is a pre-existing limitation that exists even on the main branch, unrelated to any specific PR. It is distinct from the simple getter form `() => boolean` which is supported. Predicate support in `queryOptions` would require a separate feature implementation.

If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@ousamabenyounes ousamabenyounes force-pushed the fix/issue-8199 branch 5 times, most recently from 61b722b to 61b9a18 Compare May 11, 2026 08:20
ousamabenyounes and others added 2 commits May 22, 2026 20:22
…Stack#8199)

The mapped UseQueryOptions type wrapped queryKey in
MaybeRefDeep<DeepUnwrapRef<TQueryKey>>. The recursive DeepUnwrapRef on a
generic TQueryKey is opaque to TS's homomorphic mapped type inference,
so TQueryKey collapsed to its default (QueryKey) when useQuery was
called from a composable that propagates a generic into the queryKey.

For the queryKey property only, drop DeepUnwrapRef and use a single-
level MaybeRef<TQueryKey> wrap. Inference now binds TQueryKey to the
supplied queryKey shape; queryFn still receives DeepUnwrapRef<TQueryKey>
via the existing mapping for non-queryKey properties.

Co-Authored-By: Ora Studio <noreply@oratelecom.net>
…xplicit TData

Per CodeRabbit feedback: bare assertType(useBasket('fruit')) only verified
the call compiled, which would still pass under the old buggy behavior
(TQueryKey collapsing to QueryKey, TData inferred as unknown). Lock the
regression by asserting the propagated TData explicitly.
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.

[vue-query] type error with generic query params

1 participant