Skip to content

Commit e766740

Browse files
committed
Fix optimistic state not being replaced by server data in writeInsert
Fixes #814 When writeInsert() is called from within an onInsert handler, the transaction is in 'persisting' state. Previously, commitPendingTransactions() would re-apply optimistic state from all persisting transactions after syncing server data, causing server-generated fields to be overwritten with optimistic client-side values. This fix prevents re-applying optimistic state from persisting transactions for keys that were just synced, but only for non-truncate operations. Truncate operations still preserve optimistic state as expected. The flow now works correctly: 1. User inserts item with temporary ID -> optimistic state shows temp ID 2. Transaction commits -> onInsert handler called (state = 'persisting') 3. Handler calls writeInsert(serverItem) with real ID 4. commitPendingTransactions() syncs server data for that key 5. Optimistic state from persisting transaction is NOT re-applied for that key 6. UI now shows server data with real ID 7. Transaction completes -> optimistic state is cleared For truncate operations, optimistic state is always preserved to ensure transactions started after the truncate snapshot are maintained.
1 parent 5fb76fe commit e766740

File tree

2 files changed

+50
-0
lines changed

2 files changed

+50
-0
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
---
2+
"@tanstack/db": patch
3+
---
4+
5+
Fix optimistic state not being replaced by server data when using writeInsert in mutation handlers
6+
7+
Previously, when using `writeInsert()` inside an `onInsert` handler to sync server-generated data back to the collection, the optimistic client-side data would not be replaced by the actual server data. This meant that temporary client-side values (like negative IDs) would persist even after the server returned the real values.
8+
9+
**Example of the issue:**
10+
11+
```ts
12+
const todosCollection = createCollection(
13+
queryCollectionOptions({
14+
// ...
15+
onInsert: async ({ transaction }) => {
16+
const newItems = transaction.mutations.map((m) => m.modified)
17+
const serverItems = await createTodos(newItems)
18+
19+
todosCollection.utils.writeBatch(() => {
20+
serverItems.forEach((serverItem) => {
21+
todosCollection.utils.writeInsert(serverItem)
22+
})
23+
})
24+
25+
return { refetch: false }
26+
},
27+
})
28+
)
29+
30+
// User inserts with temporary ID
31+
todosCollection.insert({
32+
id: -1234, // Temporary negative ID
33+
title: "Task",
34+
})
35+
36+
// Server returns real ID, but UI would still show -1234 instead of the real ID
37+
```
38+
39+
This has been fixed by preventing optimistic state from `persisting` transactions from being re-applied during server data synchronization. The UI now correctly updates to show server-generated values once they are synced via `writeInsert()`.

packages/db/src/collection/state.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,17 @@ export class CollectionStateManager<
674674
this.isThisCollection(mutation.collection) &&
675675
mutation.optimistic
676676
) {
677+
// Skip re-applying optimistic state for persisting transactions
678+
// if the mutation key was just synced (prevents overwriting fresh server data)
679+
// EXCEPT during truncate operations where optimistic state should always be preserved
680+
if (
681+
!hasTruncateSync &&
682+
transaction.state === `persisting` &&
683+
changedKeys.has(mutation.key)
684+
) {
685+
continue
686+
}
687+
677688
switch (mutation.type) {
678689
case `insert`:
679690
case `update`:

0 commit comments

Comments
 (0)