fix(db): useLiveInfiniteQuery pagination with async on-demand loadSubset#1209
fix(db): useLiveInfiniteQuery pagination with async on-demand loadSubset#1209KyleAMathews wants to merge 9 commits intomainfrom
Conversation
This test documents a bug where useLiveInfiniteQuery doesn't request
pageSize+1 items from loadSubset for hasNextPage peek-ahead detection.
The bug causes hasNextPage to always return false when using on-demand
sync mode with Electric collections, because:
1. useLiveInfiniteQuery calls setWindow({ limit: pageSize + 1 }) in useEffect
2. But subscribeToOrderedChanges calls requestLimitedSnapshot BEFORE the
useEffect runs, using the original compiled limit (pageSize)
3. The loadSubset function receives limit=pageSize instead of limit=pageSize+1
4. This prevents the peek-ahead strategy from working correctly
Related: Discord bug report about useLiveInfiniteQuery + Electric on-demand
https://claude.ai/code/session_01FskX2noxNAj1zCiALFQXnC
🦋 Changeset detectedLatest commit: 2186630 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
More templates
@tanstack/angular-db
@tanstack/db
@tanstack/db-ivm
@tanstack/electric-db-collection
@tanstack/offline-transactions
@tanstack/powersync-db-collection
@tanstack/query-db-collection
@tanstack/react-db
@tanstack/rxdb-db-collection
@tanstack/solid-db
@tanstack/svelte-db
@tanstack/trailbase-db-collection
@tanstack/vue-db
commit: |
|
Size Change: +10 B (+0.01%) Total Size: 92 kB
ℹ️ View Unchanged
|
|
Size Change: 0 B Total Size: 3.7 kB ℹ️ View Unchanged
|
The initial query was using `.limit(pageSize)` but `setWindow` expects `pageSize + 1` for peek-ahead detection. This caused a race condition where the first `requestLimitedSnapshot` was called with `limit = pageSize` before `setWindow` could adjust it to `pageSize + 1`. The fix uses `pageSize + 1` from the start so the compiled query includes the peek-ahead limit, ensuring `loadSubset` receives the correct limit for `hasNextPage` detection. https://claude.ai/code/session_01FskX2noxNAj1zCiALFQXnC
- Fix unused parameter lint warnings (allPages -> _allPages) - Simplify test logic using .find() instead of .filter()[0] - Condense redundant comments Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
samwillis
left a comment
There was a problem hiding this comment.
I think this looks good, I believe the bug was that the current tests don't trigger a pushdown to load more data from the server. So while we are calling fetchNextPage to paginate, and are checking the behaviour of hasNextPage, it's not checked against on-demand collections.
I would suggest one thing, the new test is checking for a specific internal detail (we query for offset+), but really the tests should be asserting that an on demand collection as the source with incremental sync does work with useLiveInfiniteQuery. Maybe ask Claude to add those tests.
Replaces the previous implementation-detail test with a proper e2e test that verifies the actual behavior of useLiveInfiniteQuery with on-demand collections: - Initial page loads correctly with hasNextPage=true - fetchNextPage() actually loads more data via loadSubset - Multiple pages can be fetched with correct items - hasNextPage correctly reflects when no more data exists This test catches bugs where the incremental sync doesn't properly fetch data from the backend when paginating. https://claude.ai/code/session_01FskX2noxNAj1zCiALFQXnC
This test reproduces a bug where useLiveInfiniteQuery doesn't fetch subsequent pages when loadSubset returns a Promise (async mode). Root cause identified in collection-subscriber.ts: - When loadSubset returns a Promise, pendingOrderedLoadPromise is set - loadMoreIfNeeded returns early while the promise is pending - When the promise resolves, pendingOrderedLoadPromise is cleared - BUT loadMoreIfNeeded is NOT re-triggered to check if more data is needed This affects Electric on-demand mode where all data comes from async loadSubset calls. The initial page loads correctly, but fetchNextPage fails to trigger additional loadSubset calls. https://claude.ai/code/session_01FskX2noxNAj1zCiALFQXnC
When useLiveInfiniteQuery uses an on-demand collection with async loadSubset, the second page was never loaded because: 1. When setWindow() was called for the next page, maybeRunGraph's callback was never called because the graph had no pending work This fix ensures the graph run callback is called at least once even when there's no pending work, so setWindow() can trigger loadMoreIfNeeded for lazy loading scenarios. https://claude.ai/code/session_01FskX2noxNAj1zCiALFQXnC
62e4bc3 to
2186630
Compare
Summary
Fixes
useLiveInfiniteQuerynot fetching subsequent pages when used with an on-demand collection that has an asyncloadSubsetimplementation (like Electric).Two bugs were identified and fixed:
Peek-ahead detection: The initial query used
.limit(pageSize)instead of.limit(pageSize + 1), sohasNextPagewas alwaysfalseAsync pagination: When
setWindow()was called for the next page,loadMoreIfNeededwas never triggered because the graph had no pending workChanges
useLiveInfiniteQuery.ts: UsepageSize + 1in initial query for peek-aheadcollection-config-builder.ts: Call the graph callback at least once even when there's no pending work, sosetWindow()can trigger lazy loading