Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .changeset/xtjpqj-ah-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
'@tanstack/db': patch
'@tanstack/react-db': patch
---

fix(db): reject preload() promise when collection transitions to error state

Previously, `preload()` only resolved when the collection became ready. If
the collection transitioned to the `error` state while the promise was pending
(e.g. because the `queryFn` threw), the promise would hang forever, keeping
any `<Suspense>` boundary suspended indefinitely and preventing the error from
reaching an `<ErrorBoundary>`.

Now `preload()` subscribes to `status:change` events and rejects the promise
when the collection enters the `error` state. `useLiveSuspenseQuery` is also
updated to re-throw the actual error from `collection.utils?.lastError` instead
of a generic fallback message, so `<ErrorBoundary>` receives the original error.

Fixes #1343
12 changes: 12 additions & 0 deletions packages/db/src/collection/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,20 @@ export class CollectionSyncManager<
return
}

// Subscribe to status changes to reject the promise when collection errors.
// This is necessary because onFirstReady only fires on success, but if the
// collection transitions to 'error' while the promise is pending it would
// hang forever and keep the Suspense boundary suspended indefinitely.
const unsubscribeError = this._events.on(`status:change`, (event) => {
if (event.status === `error`) {
unsubscribeError()
reject(new CollectionIsInErrorStateError())
}
})

// Register callback BEFORE starting sync to avoid race condition
this.lifecycle.onFirstReady(() => {
unsubscribeError()
resolve()
})

Expand Down
10 changes: 7 additions & 3 deletions packages/react-db/src/useLiveSuspenseQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,13 @@ export function useLiveSuspenseQuery(
// After success, errors surface as stale data (matches TanStack Query behavior)
if (result.status === `error` && !hasBeenReadyRef.current) {
promiseRef.current = null
// TODO: Once collections hold a reference to their last error object (#671),
// we should rethrow that actual error instead of creating a generic message
throw new Error(`Collection "${result.collection.id}" failed to load`)
// Re-throw the actual error from the collection if available (e.g. from
// query-db-collection's utils.lastError), otherwise fall back to a generic
// message. Throwing here propagates the error to the nearest ErrorBoundary.
const lastError = (result.collection as any).utils?.lastError
throw lastError instanceof Error
? lastError
: new Error(`Collection "${result.collection.id}" failed to load`)
}

if (result.status === `loading` || result.status === `idle`) {
Expand Down
Loading