useLiveSuspenseQuery never releases over an on-demand collection whose loadSubset returns a Promise

Open
#1,858 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
55/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Active
Tech stack
react, typescript
Domain
frontend

Research direction

Start with the useLiveSuspenseQuery implementation and run the supplied @vitest/browser reproduction with npm install && npx vitest run. Compare the synchronous true and Promise-returning loadSubset cases, then verify that an async load suspends once, releases when it resolves, and does not repeatedly recreate the live query.

Written by the indexing model from the issue text.

Description

  • I've validated the bug against the latest version of DB packages (@tanstack/db@0.9.2, @tanstack/react-db@0.4.1)

Describe the bug

useLiveSuspenseQuery over an on-demand collection never leaves its Suspense fallback whenever loadSubset returns a Promise — which is every real asynchronous data source.

LoadSubsetFn is declared (options) => true | Promise<void>. If an implementation answers with the synchronous true, the boundary releases immediately. If it answers with a promise — even one that resolves on the next tick — the component re-suspends without limit.

To Reproduce

Runnable repro, real Chromium via @vitest/browser: https://gist.github.com/MAST1999/c2261d268a68610f18c5a6c45525b468

npm install && npx vitest run

Plain @tanstack/db. No persistence, no Electric, no orderBy, no limit. The only variable between passing and failing is whether loadSubset returns true or a Promise; both deliver the same two rows.

✓ a synchronous loadSubset releases the boundary                      54ms
× an async loadSubset resolving after   0ms   → text="loading" renders=23525
× an async loadSubset resolving after  50ms   → text="loading" renders=416
× an async loadSubset resolving after 500ms   → text="loading" renders=56
× an explicit queryKey does not change the outcome
× StrictMode is not the cause

Two things a reviewer would reasonably suspect are ruled out in the repro itself: passing an explicit stable queryKey does not help, and it reproduces with StrictMode removed.

The render count falling as the delay grows (23525 → 416 → 56) is the signature of a re-suspend cycle rather than a busy loop — each retry waits for the promise, so a slower promise fits fewer retries into the same five-second budget.

Expected behavior

An async loadSubset should suspend once and release when it resolves.

Likely mechanism

useLiveSuspenseQuery resets promiseRef/hasBeenReadyRef whenever collectionRef.current !== result.collection and throws observer.preload(). A component that suspends before it has ever mounted does not keep its hook state, so each retry constructs a fresh live query, which is loading, which throws a fresh preload promise, which resolves, which retries. The synchronous-true path never suspends at all, which is why it is the only passing case.

I have not confirmed the React-internals half of that (whether a fiber that suspends pre-mount discards its hook state), so treat the mechanism as a hypothesis and the measurements above as the report.

Additional context

This looks like the general case of two issues I filed earlier today, both of which are ways of arriving at a promise-returning loadSubset:

  • #1855 — orderBy + limit makes the ordered loader chain subset requests, so the result is never synchronously ready.
  • #1856 — the SQLite persistence wrapper declares its loadSubset async, so it is a promise even when the wrapped sync answered true.

If this one is fixed, both of those stop being user-visible hangs and become merely redundant work. I'd suggest triaging this first.

Dominant language
TypeScript
Stars
3.9k
Forks
266
Avg merge
1d 4h
Merged PRs (30d)
56

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from TanStack/db

All issues in TanStack/db

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.