Skip to content

fix(react-db): use collection.status instead of snapshot in useLiveSuspenseQuery to avoid infinite fallback loops#1427

Open
calcari wants to merge 1 commit intoTanStack:mainfrom
calcari:main
Open

fix(react-db): use collection.status instead of snapshot in useLiveSuspenseQuery to avoid infinite fallback loops#1427
calcari wants to merge 1 commit intoTanStack:mainfrom
calcari:main

Conversation

@calcari
Copy link
Copy Markdown

@calcari calcari commented Mar 28, 2026

Fixes #1418

In useLiveSuspenseQuery.ts
result.status comes from the snapshot in useLiveQuery.ts
result.collection.status is a live reference

This may cause an inconsistent render where:

  • result.status === "loading"
  • result.collection.status === "ready"

The react documentation says "It’s not recommended to suspend a render based on a store value returned by useSyncExternalStore." so reading the fresh status from the mutable collection seems correct here.

Switching to result.collection.status ensures Suspense uses the latest state and prevents infinite fallback loops.

-    if (result.status === `loading` || result.status === `idle`) {
+    if (result.collection.status === `loading` || result.collection.status === `idle`) {
        // Create or reuse promise for current collection
        if (!promiseRef.current) {
            promiseRef.current = result.collection.preload()
        }
        // THROW PROMISE - React Suspense catches this (React 18+ required)
        // Note: We don't check React version here. In React <18, this will be caught
        // by an Error Boundary, which provides a reasonable failure mode.
        throw promiseRef.current
    }

Demo

I could not add a reliable RTL/jsdom test for this React 19 Suspense scenario, as it appears to hit the limitation discussed in testing-library/react-testing-library#1375.

As a fallback, I added a small demo showing the fix in action:

StackBlitz

  • Open tab n°4 (fix)
  • Click Next

🎯 Changes

  • introduce collectionStatus variable
  • replace all status checks (loading, idle, error, ready) with collectionStatus
  • move isEnabled check before accessing result.collection

✅ Checklist

  • I have tested this code locally with pnpm test.

🚀 Release Impact

  • This change affects published code, and I have generated a [changeset]
  • This change is docs/CI/dev-only (no release).

…uspenseQuery (TanStack#1418)

result.status can be stale because it comes from the useSyncExternalStore snapshot,
while result.collection is a live mutable reference.

This may cause an inconsistent render where:
- result.status === "loading"
- result.collection.status === "ready"

Switching to result.collection.status ensures Suspense uses the latest state
and prevents infinite fallback loops.

Changes:
- introduce collectionStatus variable
- replace all status checks (loading, idle, error, ready) with collectionStatus
- move isEnabled check before accessing result.collection
Copy link
Copy Markdown
Contributor

@kevin-dp kevin-dp left a comment

Choose a reason for hiding this comment

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

Thanks for catching and fixing this!

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Mar 30, 2026

More templates

@tanstack/angular-db

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

@tanstack/browser-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/browser-db-sqlite-persistence@1427

@tanstack/capacitor-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/capacitor-db-sqlite-persistence@1427

@tanstack/cloudflare-durable-objects-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/cloudflare-durable-objects-db-sqlite-persistence@1427

@tanstack/db

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

@tanstack/db-ivm

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

@tanstack/db-sqlite-persistence-core

npm i https://pkg.pr.new/@tanstack/db-sqlite-persistence-core@1427

@tanstack/electric-db-collection

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

@tanstack/electron-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/electron-db-sqlite-persistence@1427

@tanstack/expo-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/expo-db-sqlite-persistence@1427

@tanstack/node-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/node-db-sqlite-persistence@1427

@tanstack/offline-transactions

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

@tanstack/powersync-db-collection

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

@tanstack/query-db-collection

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

@tanstack/react-db

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

@tanstack/react-native-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/react-native-db-sqlite-persistence@1427

@tanstack/rxdb-db-collection

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

@tanstack/solid-db

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

@tanstack/svelte-db

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

@tanstack/tauri-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/tauri-db-sqlite-persistence@1427

@tanstack/trailbase-db-collection

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

@tanstack/vue-db

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

commit: df9ef78

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.

useLiveSuspenseQuery stays stuck on suspense fallback with on-demand queryCollection after dependency change

2 participants