Skip to content

docs(vue-query): explain why reactive() is needed for result narrowing - #10580

Open
ousamabenyounes wants to merge 1 commit into
TanStack:mainfrom
ousamabenyounes:fix/issue-9244
Open

docs(vue-query): explain why reactive() is needed for result narrowing#10580
ousamabenyounes wants to merge 1 commit into
TanStack:mainfrom
ousamabenyounes:fix/issue-9244

Conversation

@ousamabenyounes

@ousamabenyounesousamabenyounes commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Follow-up to the discussion in #9244 (does not close it).

docs/framework/vue/typescript.md shows the type-narrowing example wrapped in reactive() but never says why, so the natural reading is that destructuring useQuery() directly and then checking isSuccess narrows data as well. It does not — destructuring yields independent refs, and TypeScript cannot carry a narrowing from isSuccess to data.

This PR says that explicitly and shows the alternative when reactive() is not an option (data.value !== undefined), plus two type tests pinning both shapes:

  • the non-reactive result exposes Ref<T> | Ref<undefined> (the shape already documented just above the example);
  • narrowing on data.value refines the ref itself to Ref<T>.

Docs and tests only, no runtime or type change, so no changeset.

Note on the previous revision

This branch previously carried a change to UseBaseQueryReturnType that made the mapped type explicitly distributive. That was a no-op: { [K in keyof TResult]: ... } is homomorphic and already distributes over the union, and the two formulations are mutually assignable for QueryObserverResult, DefinedQueryObserverResult and InfiniteQueryObserverResult. The type tests added here pass with or without it, so I dropped it instead of shipping a diff with no effect.

The underlying request in #9244 needs the core result union to carry Ref in its signature, as noted in that thread. That is breaking for every adapter and out of scope here.

Summary by CodeRabbit

  • Documentation

    • Clarified how to use reactive() with useQuery() for improved TypeScript narrowing.
    • Documented the correct guard for narrowing data when destructuring query results.
  • Tests

    • Added type coverage confirming accurate handling and narrowing of query data refs without reactive().

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation package: vue-query labels Apr 25, 2026
@coderabbitai

coderabbitaiBot commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f623963e-da67-4b32-97e7-36b09bdee3c8

📥 Commits

Reviewing files that changed from the base of the PR and between 1ef4208 and b56dd6c.

📒 Files selected for processing (2)
  • docs/framework/vue/typescript.md
  • packages/vue-query/src/__tests__/useQuery.test-d.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/framework/vue/typescript.md

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The changes document TypeScript narrowing for Vue Query results and add regression tests for non-reactive() query results. The tests verify narrowing through data.value !== undefined.

Changes

Vue Query type narrowing

Layer / File(s)Summary
Direct ref narrowing tests and documentation
packages/vue-query/src/__tests__/useQuery.test-d.ts, docs/framework/vue/typescript.md
Adds type tests for `Ref

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk:⚪ Minimal · up to b56dd

This PR clarifies Vue Query type-narrowing behavior and adds type tests without changing runtime behavior, so no actionable merge-blocking risk remains after normal checks and review.

Possibly related PRs

  • TanStack/query#11164: Addresses TypeScript discriminated-union narrowing in query hook results for a different framework.

Suggested reviewers:tkdodo

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedThe PR addresses [#9244] by documenting supported narrowing workarounds and adding type tests for the non-reactive result shape.
Out of Scope Changes check✅ PassedThe changes are limited to Vue TypeScript documentation and regression type tests, with no unrelated runtime or public type changes.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check✅ PassedThe title clearly identifies the Vue documentation change and its purpose: explaining why reactive() is needed for result narrowing.
Description check✅ PassedThe description explains the motivation, scope, tests, and absence of a release impact, although it does not use the repository template headings or checklist.
✨ 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.

@nx-cloud

nx-cloudBot commented Apr 25, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 2163cc3

CommandStatusDurationResult
nx run-many --target=build --exclude=examples/*...✅ Succeeded<1sView ↗
nx affected --targets=test:sherif,test:knip,tes...✅ Succeeded17sView ↗

☁️ Nx Cloud last updated this comment at 2026-04-28 15:54:09 UTC

@pkg-pr-new

pkg-pr-newBot commented Apr 25, 2026

Copy link
Copy Markdown
More templates

@tanstack/angular-query-experimental

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

@tanstack/eslint-plugin-query

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

@tanstack/preact-query

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

@tanstack/preact-query-devtools

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

@tanstack/preact-query-persist-client

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

@tanstack/query-async-storage-persister

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

@tanstack/query-broadcast-client-experimental

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

@tanstack/query-core

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

@tanstack/query-devtools

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

@tanstack/query-persist-client-core

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

@tanstack/query-sync-storage-persister

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

@tanstack/react-query

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

@tanstack/react-query-devtools

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

@tanstack/react-query-next-experimental

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

@tanstack/react-query-persist-client

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

@tanstack/solid-query

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

@tanstack/solid-query-devtools

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

@tanstack/solid-query-persist-client

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

@tanstack/svelte-query

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

@tanstack/svelte-query-devtools

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

@tanstack/svelte-query-persist-client

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

@tanstack/vue-query

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

@tanstack/vue-query-devtools

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

commit: 2163cc3

@coderabbitaicoderabbitaiBot left a comment

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.

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

📥 Commits

Reviewing files that changed from the base of the PR and between f85d825 and 7042fa9.

📒 Files selected for processing (4)
  • .changeset/vue-query-narrow-result-type.md
  • docs/framework/vue/typescript.md
  • packages/vue-query/src/__tests__/useQuery.test-d.ts
  • packages/vue-query/src/useBaseQuery.ts

Comment threadpackages/vue-query/src/useBaseQuery.ts Outdated
@ousamabenyounes

Copy link
Copy Markdown
ContributorAuthor

Thanks @coderabbitai — addressed in b69f079. Lifted suspense outside the distributive conditional and typed it as Promise<TResult>, so awaiting suspense() now preserves the parameterized observer shape (DefinedQueryObserverResult keeps non-undefined data, InfiniteQueryObserverResult keeps fetchNextPage / hasNextPage / data.pages). Aligns with the runtime returned by observer.fetchOptimistic().

@coderabbitai

Copy link
Copy Markdown
Contributor

Tip

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

@ousamabenyounes That's a better approach — using Promise<TResult> rather than the pinned Promise<QueryObserverResult<TData, TError>> means callers working with specialized result variants (e.g. DefinedQueryObserverResult or InfiniteQueryObserverResult) get the correct, tighter type back from await suspense() without losing properties like data.pages or the non-undefined data guarantee. Thanks for the thorough explanation!


✏️ Learnings added
Learnt from: ousamabenyounes
URL: https://github.com/TanStack/query/pull/10580
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()`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
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.

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.

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

Thank you for using CodeRabbit!

ousamabenyounes added a commit to ousamabenyounes/query that referenced this pull request Apr 26, 2026
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.
ousamabenyounes added a commit to ousamabenyounes/query that referenced this pull request May 22, 2026
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.
The type narrowing example wraps useQuery() in reactive() without saying
why, so destructuring the result directly and then checking isSuccess
looks like it should work (TanStack#9244). Spell out that reactive() is what
flattens the refs into one discriminated union, and show the
data.value !== undefined check as the alternative when reactive() is not
an option.
Pin both shapes with type tests: the non-reactive result exposes
Ref<T> | Ref<undefined>, and narrowing on data.value refines the ref
itself.
@ousamabenyounesousamabenyounes changed the title fix(vue-query): preserve discriminated union narrowing in UseBaseQueryReturnType (#9244)docs(vue-query): explain why reactive() is needed for result narrowingAug 20, 2026
@ousamabenyounes

Copy link
Copy Markdown
ContributorAuthor

Rebased onto main and rescoped in b56dd6c.

The UseBaseQueryReturnType change I had here was a no-op — the mapped type is homomorphic, so it already distributes over the result union, and the two formulations are mutually assignable for QueryObserverResult, DefinedQueryObserverResult and InfiniteQueryObserverResult. The type tests I added pass without it, so I dropped it along with the changeset and the Fixes #9244 claim.

What is left is docs plus two type tests: the narrowing section now says why reactive() is required and shows the data.value !== undefined alternative. The actual ask in #9244 needs Ref inside the core result union, which is out of scope here.

@coderabbitai

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationpackage: vue-query

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@ousamabenyounes