Skip to content
Open
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
25 changes: 22 additions & 3 deletions packages/react-db/src/useLiveQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -434,17 +434,36 @@ export function useLiveQuery(
return () => {}
}

let unsubscribed = false

const subscription = collectionRef.current.subscribeChanges(() => {
// The subscription can outlive the React subscription window when an
// already-queued change arrives between `unsubscribed = true` and the
// underlying `subscription.unsubscribe()`. Drop the late notify so
// React never sees a state update post-unsubscribe.
if (unsubscribed) return
// Bump version on any change; getSnapshot will rebuild next time
versionRef.current += 1
onStoreChange()
})
// Collection may be ready and will not receive initial `subscribeChanges()`
// Collection may be ready and will not receive initial `subscribeChanges()`.
// We must notify React so it picks up the ready state — but doing it
// synchronously here lands during the render-to-commit window when
// `useSyncExternalStore`'s subscribe runs in StrictMode double-render
// or under cold/throttled loads, which React surfaces as:
// "Can't perform a React state update on a component that hasn't
// mounted yet. ... Move this work to useEffect instead."
// Defer to a microtask so the notify lands AFTER the current commit.
// See #1587 for the Lighthouse-cold-load repro.
if (collectionRef.current.status === `ready`) {
versionRef.current += 1
onStoreChange()
queueMicrotask(() => {
if (unsubscribed) return
versionRef.current += 1
onStoreChange()
})
}
return () => {
unsubscribed = true
subscription.unsubscribe()
}
}
Expand Down