Skip to content

Conversation

@KyleAMathews
Copy link
Collaborator

Make it clear this isn't supported and they should use useLiveQuery instead.

- Add overloads to support query functions that can return undefined/null
- Update implementation to return undefined values instead of throwing error when query is disabled
- Add tests for conditional query pattern with undefined
- Mirrors useLiveQuery behavior for consistency

This allows conditional queries like:
```ts
useLiveSuspenseQuery(
  (q) => userId
    ? q.from({ users }).where(({ users }) => eq(users.id, userId)).findOne()
    : undefined,
  [userId]
)
```
Following TanStack Query's useSuspenseQuery design philosophy, disabled
queries are intentionally not supported to maintain the type guarantee
that data is T (not T | undefined).

Changes:
- Revert type overloads that allowed undefined/null returns
- Keep error throw when query callback returns undefined/null
- Improve error message with clear guidance on alternatives:
  1. Use conditional rendering (don't render component until ready)
  2. Use useLiveQuery instead (supports isEnabled flag)
- Update tests to expect error instead of undefined values
- Update changeset to document the design decision and alternatives

This matches TanStack Query's approach where Suspense queries prioritize
type safety and proper component composition over flexibility.
@changeset-bot
Copy link

changeset-bot bot commented Nov 19, 2025

🦋 Changeset detected

Latest commit: fbed864

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@tanstack/react-db Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 19, 2025

More templates

@tanstack/angular-db

npm i https://pkg.pr.new/@tanstack/angular-db@860

@tanstack/db

npm i https://pkg.pr.new/@tanstack/db@860

@tanstack/db-ivm

npm i https://pkg.pr.new/@tanstack/db-ivm@860

@tanstack/electric-db-collection

npm i https://pkg.pr.new/@tanstack/electric-db-collection@860

@tanstack/offline-transactions

npm i https://pkg.pr.new/@tanstack/offline-transactions@860

@tanstack/powersync-db-collection

npm i https://pkg.pr.new/@tanstack/powersync-db-collection@860

@tanstack/query-db-collection

npm i https://pkg.pr.new/@tanstack/query-db-collection@860

@tanstack/react-db

npm i https://pkg.pr.new/@tanstack/react-db@860

@tanstack/rxdb-db-collection

npm i https://pkg.pr.new/@tanstack/rxdb-db-collection@860

@tanstack/solid-db

npm i https://pkg.pr.new/@tanstack/solid-db@860

@tanstack/svelte-db

npm i https://pkg.pr.new/@tanstack/svelte-db@860

@tanstack/trailbase-db-collection

npm i https://pkg.pr.new/@tanstack/trailbase-db-collection@860

@tanstack/vue-db

npm i https://pkg.pr.new/@tanstack/vue-db@860

commit: fbed864

@github-actions
Copy link
Contributor

github-actions bot commented Nov 19, 2025

Size Change: 0 B

Total Size: 85.8 kB

ℹ️ View Unchanged
Filename Size
./packages/db/dist/esm/collection/change-events.js 1.38 kB
./packages/db/dist/esm/collection/changes.js 977 B
./packages/db/dist/esm/collection/events.js 388 B
./packages/db/dist/esm/collection/index.js 3.24 kB
./packages/db/dist/esm/collection/indexes.js 1.1 kB
./packages/db/dist/esm/collection/lifecycle.js 1.67 kB
./packages/db/dist/esm/collection/mutations.js 2.26 kB
./packages/db/dist/esm/collection/state.js 3.43 kB
./packages/db/dist/esm/collection/subscription.js 2.42 kB
./packages/db/dist/esm/collection/sync.js 2.12 kB
./packages/db/dist/esm/deferred.js 207 B
./packages/db/dist/esm/errors.js 4.11 kB
./packages/db/dist/esm/event-emitter.js 748 B
./packages/db/dist/esm/index.js 2.63 kB
./packages/db/dist/esm/indexes/auto-index.js 742 B
./packages/db/dist/esm/indexes/base-index.js 766 B
./packages/db/dist/esm/indexes/btree-index.js 1.87 kB
./packages/db/dist/esm/indexes/lazy-index.js 1.1 kB
./packages/db/dist/esm/indexes/reverse-index.js 513 B
./packages/db/dist/esm/local-only.js 837 B
./packages/db/dist/esm/local-storage.js 2.1 kB
./packages/db/dist/esm/optimistic-action.js 359 B
./packages/db/dist/esm/paced-mutations.js 496 B
./packages/db/dist/esm/proxy.js 3.22 kB
./packages/db/dist/esm/query/builder/functions.js 733 B
./packages/db/dist/esm/query/builder/index.js 3.84 kB
./packages/db/dist/esm/query/builder/ref-proxy.js 917 B
./packages/db/dist/esm/query/compiler/evaluators.js 1.35 kB
./packages/db/dist/esm/query/compiler/expressions.js 430 B
./packages/db/dist/esm/query/compiler/group-by.js 1.8 kB
./packages/db/dist/esm/query/compiler/index.js 1.96 kB
./packages/db/dist/esm/query/compiler/joins.js 2 kB
./packages/db/dist/esm/query/compiler/order-by.js 1.25 kB
./packages/db/dist/esm/query/compiler/select.js 1.07 kB
./packages/db/dist/esm/query/expression-helpers.js 1.43 kB
./packages/db/dist/esm/query/ir.js 673 B
./packages/db/dist/esm/query/live-query-collection.js 360 B
./packages/db/dist/esm/query/live/collection-config-builder.js 5.26 kB
./packages/db/dist/esm/query/live/collection-registry.js 264 B
./packages/db/dist/esm/query/live/collection-subscriber.js 1.74 kB
./packages/db/dist/esm/query/live/internal.js 130 B
./packages/db/dist/esm/query/optimizer.js 2.56 kB
./packages/db/dist/esm/query/predicate-utils.js 2.88 kB
./packages/db/dist/esm/query/subset-dedupe.js 921 B
./packages/db/dist/esm/scheduler.js 1.21 kB
./packages/db/dist/esm/SortedMap.js 1.18 kB
./packages/db/dist/esm/strategies/debounceStrategy.js 237 B
./packages/db/dist/esm/strategies/queueStrategy.js 422 B
./packages/db/dist/esm/strategies/throttleStrategy.js 236 B
./packages/db/dist/esm/transactions.js 2.9 kB
./packages/db/dist/esm/utils.js 881 B
./packages/db/dist/esm/utils/browser-polyfills.js 304 B
./packages/db/dist/esm/utils/btree.js 5.61 kB
./packages/db/dist/esm/utils/comparison.js 852 B
./packages/db/dist/esm/utils/index-optimization.js 1.51 kB
./packages/db/dist/esm/utils/type-guards.js 157 B

compressed-size-action::db-package-size

@github-actions
Copy link
Contributor

github-actions bot commented Nov 19, 2025

Size Change: 0 B

Total Size: 3.34 kB

ℹ️ View Unchanged
Filename Size
./packages/react-db/dist/esm/index.js 225 B
./packages/react-db/dist/esm/useLiveInfiniteQuery.js 1.17 kB
./packages/react-db/dist/esm/useLiveQuery.js 1.11 kB
./packages/react-db/dist/esm/useLiveSuspenseQuery.js 431 B
./packages/react-db/dist/esm/usePacedMutations.js 401 B

compressed-size-action::react-db-package-size

Add detailed @remarks section that appears in IDE tooltips to clearly
explain that disabled queries are not supported and provide two
alternative solutions:
1. Use conditional rendering (recommended pattern)
2. Use useLiveQuery instead (supports isEnabled flag)

Includes clear examples showing both ❌ incorrect and ✅ correct patterns.

This provides immediate guidance when users encounter the type error,
without requiring complex type-level error messages that can be fragile.
TypeScript's natural "No overload matches this call" error combined
with the JSDoc tooltip provides a good developer experience.
Add 'poison pill' overloads that return DisabledQueryError type with
custom error message embedded via unique symbol. This is experimental
to evaluate if it provides better DX than JSDoc alone.

Still evaluating trade-offs before finalizing approach.
The key insight: make the implementation signature return type a union
that includes both DisabledQueryError and the valid return types.

TypeScript requires overload signatures to be compatible with the
implementation signature. By using a union type:
  DisabledQueryError | { state: any; data: any; collection: any }

We satisfy TypeScript's compatibility requirement while still catching
invalid patterns at compile time.

What users experience:
1. Type error when returning undefined → DisabledQueryError inferred
2. Property access errors: "Property 'data' does not exist on type 'DisabledQueryError'"
3. IDE tooltip shows custom error message embedded in the type
4. Compilation fails (forces fix)

This provides BOTH:
- JSDoc documentation (in tooltips)
- Active type-level errors with custom messaging
The poison pill overloads were matching BEFORE the specific overloads,
causing TypeScript to infer DisabledQueryError for valid queries.

TypeScript checks overloads top-to-bottom and uses the first match.
Since QueryBuilder is assignable to QueryBuilder | undefined | null,
the poison pill overloads were matching first.

Solution: Move poison pill overloads to the END, just before the
implementation. This ensures:
1. Specific overloads (without undefined) match first
2. Poison pill overloads (with undefined) only match when needed

All tests now pass with no type errors.
Copy link
Collaborator

@samwillis samwillis left a comment

Choose a reason for hiding this comment

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

I pulled this locally as I wanted to see what the "poison pill" overload would do, and particularly how it would show in an editor. It seems that it just removes the type error, and then when you scroll down (when mouse-overing the hook) you see the suggestion this will not work - see video below.

This means that while previously (see the second image) we would have a type error if the hook tried to return undefined, now it doesn't throw a type error at all.

The runtime error this pr adds looks good, but I think the new type error is a worse option than an unfortunate but correct type error.

Video showing how the new "poison pill" overload works in an editor:

Screen.Recording.2025-11-25.at.13.18.46.mov

This is on main with the same change:

Image

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants