fix(vue-query): preserve discriminated union narrowing in UseBaseQueryReturnType (#9244)#10580
fix(vue-query): preserve discriminated union narrowing in UseBaseQueryReturnType (#9244)#10580ousamabenyounes wants to merge 4 commits intoTanStack:mainfrom
Conversation
…yReturnType (TanStack#9244) Make the mapped type explicitly distributive over each variant of QueryObserverResult, and lock in the narrowing patterns that work without reactive() (direct data.value !== undefined check) versus those that require reactive() (narrowing via isSuccess / status). Fixes TanStack#9244 Generated by Claude Code Vibe coded by ousamabenyounes Co-Authored-By: Claude <noreply@anthropic.com>
📝 WalkthroughWalkthroughConverts Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
View your CI Pipeline Execution ↗ for commit 2163cc3
☁️ Nx Cloud last updated this comment at |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/vue-query/src/useBaseQuery.ts`:
- Around line 38-49: UseBaseQueryReturnType currently hardcodes the suspense
return to Promise<QueryObserverResult<TData, TError>> which loses precision for
parameterized TResult (e.g., DefinedQueryObserverResult,
InfiniteQueryObserverResult); update the type so that the mapped properties
remain distributive over TResult but the suspense property is parameterized by
TResult (i.e., make suspense: () => Promise<TResult extends unknown ? TResult :
never> or lift suspense outside the distributive conditional) so awaiting
suspense() returns the actual TResult shape (preserving
fetchNextPage/hasNextPage/data non-undefined), and ensure this aligns with the
runtime behavior of
observer.getOptimisticResult/fetchOptimistic/getCurrentResult so no other
signatures need changing.
🪄 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: 9bd7d5bb-ab5a-4222-9ec2-4d0fe0de7e76
📒 Files selected for processing (4)
.changeset/vue-query-narrow-result-type.mddocs/framework/vue/typescript.mdpackages/vue-query/src/__tests__/useQuery.test-d.tspackages/vue-query/src/useBaseQuery.ts
The previous shape pinned `suspense` to `Promise<QueryObserverResult<TData, TError>>` inside the distributive conditional, so awaiting `suspense()` lost type precision when `UseBaseQueryReturnType` was instantiated with `DefinedQueryObserverResult` (data became `TData | undefined` again) or `InfiniteQueryObserverResult` (no `fetchNextPage`/`hasNextPage`/`data.pages`). Lifting `suspense` outside the distributive arm and typing it as `Promise<TResult>` keeps the per-property mapping distributive but lets the suspense result carry the parameterized observer type — matching the runtime already returned by `observer.fetchOptimistic()` / `getOptimisticResult()`.
|
Thanks @coderabbitai — addressed in |
|
Tip For best results, initiate chat on the files or code changes.
✏️ Learnings added
🧠 Learnings usedIf you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit! |
Adds a regression test asserting that `useQuery(...).suspense()` resolves to `Promise<QueryObserverResult<TData, TError>>` (parameterized by `TResult` on `UseBaseQueryReturnType`) and that awaiting it preserves the discriminated union narrowing on `isSuccess` / `isError`. Closes the verification gap noted on TanStack#10580 — the parameterization was previously only validated via tsc build, not by an explicit `expectTypeOf` assertion.
Summary
Fixes #9244
The current
UseBaseQueryReturnTypemapped type already produces a discriminated union when instantiated with a genericTResult(homomorphic mapped types preserve unions over generic parameters), so narrowing viareactive(useQuery(...))already works as documented. However:QueryObserverResult— is implicit and easy to break in future refactors.reactive(), accessing.valueon a wrapped discriminator) are not captured by tests, so users (narrowing typescript keepsdataas possible undefined #9244) hit them without a clear answer.This PR:
UseBaseQueryReturnTypeexplicit (TResult extends unknown ? ... : never). No runtime change; same observable type for all currently-supported usages.useQuery.test-d.ts→issue #9244 — narrowing without reactive():datashape on rawuseQuery()matches the docs (Ref<T> | Ref<undefined>).if (data.value !== undefined)correctly narrowsdatatoRef<T>anddata.valuetoT(the recommended pattern whenreactive()is not available).reactive()preserves narrowing across destructured{ data, isSuccess }and across thestatus === 'success'discriminator.docs/framework/vue/typescript.mdnext to the existing narrowing example explaining whenreactive()is required and what to do otherwise.Why this isn't a deeper type-level fix
I traced through every plausible structural change (distributing over the conditional, plain-bool discriminators,
ToRefs-based variants, etc.). Cross-property narrowing withoutreactive()requires either (a) breaking the API by removingRef<>from discriminator props, which would break all existing.valueaccess at call sites, or (b) a TypeScript-level feature for narrowing throughRef.valuethat doesn't exist.reactive()is the supported escape hatch and now the docs say so directly.Verification
main: all 289 vue-query tests pass.pnpm exec vitest run --typecheck.enabledclean.pnpm exec nx run-many -t test:types -p @tanstack/vue-query @tanstack/query-core @tanstack/react-query— green across TS 5.4–6.0.pnpm exec nx run @tanstack/vue-query:test:liband:test:eslint— green.patchfor@tanstack/vue-query).Files changed
packages/vue-query/src/useBaseQuery.tsUseBaseQueryReturnTypeexplicitly distributive; pinsuspensereturn to the un-distributedQueryObserverResult<TData, TError>so its signature doesn't shift.packages/vue-query/src/__tests__/useQuery.test-d.tsdocs/framework/vue/typescript.mdreactive()is required and thedata.value !== undefinedworkaround..changeset/vue-query-narrow-result-type.mdGenerated by Ora Studio
Vibe coded by ousamabenyounes
Summary by CodeRabbit
Bug Fixes
reactive()and via directdata.value !== undefinedchecks).Documentation
reactive().Tests
Chores
Vibe Coded by Ousama Ben Younes
Developed With Ora Studio (Claude Code)