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
5 changes: 5 additions & 0 deletions .changeset/bright-beds-reflect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/react-db': patch
---

Refresh live query snapshots immediately after subscribing, even when the collection is still loading.
12 changes: 7 additions & 5 deletions packages/react-db/src/useLiveQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -439,11 +439,13 @@ export function useLiveQuery(
versionRef.current += 1
onStoreChange()
})
// Collection may be ready and will not receive initial `subscribeChanges()`
if (collectionRef.current.status === `ready`) {
versionRef.current += 1
onStoreChange()
}
// The collection may have changed between render-time getSnapshot() and
// this subscription being attached. This is common when an on-demand query
// hydrates local rows immediately but keeps the collection loading while
// remote sync finishes. Force one post-subscribe snapshot refresh
// regardless of status so React sees those rows.
versionRef.current += 1
onStoreChange()
return () => {
subscription.unsubscribe()
}
Expand Down
51 changes: 51 additions & 0 deletions packages/react-db/tests/useLiveQuery.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
mockSyncCollectionOptions,
stripVirtualProps,
} from '../../db/tests/utils'
import type { SyncConfig } from '@tanstack/db'

type Person = {
id: string
Expand Down Expand Up @@ -1559,6 +1560,56 @@ describe(`Query Collections`, () => {
})

describe(`eager execution during sync`, () => {
it(`refreshes the snapshot after subscribing while the collection is still loading`, async () => {
type TestRow = { id: string; value: number }

let syncBegin: (() => void) | undefined
let syncWrite: Parameters<SyncConfig<TestRow>[`sync`]>[0][`write`]
let syncCommit: (() => void) | undefined
let didWriteAfterSnapshot = false

const collection = createCollection<TestRow>({
id: `loading-subscribe-snapshot-refresh`,
getKey: (row) => row.id,
startSync: false,
sync: {
sync: ({ begin, write, commit }) => {
syncBegin = begin
syncWrite = write
syncCommit = commit
},
},
})

const { result } = renderHook(() => {
const queryResult = useLiveQuery(collection)

if (!didWriteAfterSnapshot) {
didWriteAfterSnapshot = true
// Simulate a collection receiving rows after useLiveQuery read its
// render-time snapshot, but before React attached the external-store
// subscription. The collection intentionally stays loading because
// the missed update should still be visible before remote sync
// finishes.
syncBegin!()
syncWrite({
type: `insert`,
value: { id: `1`, value: 1 },
})
syncCommit!()
}

return queryResult
})

await waitFor(() => {
expect(
result.current.data.map((row) => stripVirtualProps(row)),
).toEqual([{ id: `1`, value: 1 }])
})
expect(result.current.isLoading).toBe(true)
})

it(`should show state while isLoading is true during sync`, async () => {
let syncBegin: (() => void) | undefined
let syncWrite: ((op: any) => void) | undefined
Expand Down
Loading