From bfa2865b1fbe54ae0403e28f482f105420dbbbee Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 21 Oct 2025 15:06:22 +0000 Subject: [PATCH 1/2] docs: improve incremental updates example in query-collection docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace confusing manual optimistic update pattern with familiar onInsert/onUpdate handler pattern. Show how to: - Use persistence handlers (onInsert, onUpdate) like regular collections - Return { refetch: false } to avoid unnecessary refetches - Sync server-computed fields using direct writes within handlers This makes the example more approachable and consistent with the rest of the documentation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/collections/query-collection.md | 73 ++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 21 deletions(-) diff --git a/docs/collections/query-collection.md b/docs/collections/query-collection.md index 6f641dbf3..cdfe19333 100644 --- a/docs/collections/query-collection.md +++ b/docs/collections/query-collection.md @@ -224,28 +224,59 @@ ws.on('todos:update', (changes) => { ### Example: Incremental Updates +When the server returns computed fields (like server-generated IDs or timestamps), you can use the `onInsert` handler with `{ refetch: false }` to avoid unnecessary refetches while still syncing the server response: + ```typescript -// Handle server responses after mutations without full refetch -const createTodo = async (todo) => { - // Optimistically add the todo - const tempId = crypto.randomUUID() - todosCollection.insert({ ...todo, id: tempId }) - - try { - // Send to server - const serverTodo = await api.createTodo(todo) - - // Sync the server response (with server-generated ID and timestamps) - // without triggering a full collection refetch - todosCollection.utils.writeBatch(() => { - todosCollection.utils.writeDelete(tempId) - todosCollection.utils.writeInsert(serverTodo) - }) - } catch (error) { - // Rollback happens automatically - throw error - } -} +const todosCollection = createCollection( + queryCollectionOptions({ + queryKey: ['todos'], + queryFn: fetchTodos, + queryClient, + getKey: (item) => item.id, + + onInsert: async ({ transaction }) => { + const newItems = transaction.mutations.map(m => m.modified) + + // Send to server and get back items with server-computed fields + const serverItems = await api.createTodos(newItems) + + // Update the optimistic items with server-computed fields + // (like server-generated IDs, timestamps, etc.) + todosCollection.utils.writeBatch(() => { + transaction.mutations.forEach((mutation, index) => { + const serverItem = serverItems[index] + // Remove temporary optimistic item + todosCollection.utils.writeDelete(mutation.key) + // Insert with server data + todosCollection.utils.writeInsert(serverItem) + }) + }) + + // Skip automatic refetch since we've already synced the server response + return { refetch: false } + }, + + onUpdate: async ({ transaction }) => { + const updates = transaction.mutations.map(m => ({ + id: m.key, + changes: m.changes + })) + const serverItems = await api.updateTodos(updates) + + // Sync server-computed fields from the update response + todosCollection.utils.writeBatch(() => { + serverItems.forEach(serverItem => { + todosCollection.utils.writeUpdate(serverItem) + }) + }) + + return { refetch: false } + } + }) +) + +// Usage is just like a regular collection +todosCollection.insert({ text: 'Buy milk', completed: false }) ``` ### Example: Large Dataset Pagination From 206ce5889b6b83c6b5043f1df62d00a7c04e560f Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 21 Oct 2025 15:29:35 +0000 Subject: [PATCH 2/2] docs: remove unnecessary writeDelete from query-collection example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Optimistic state is automatically removed when the mutation handler returns, so there's no need to manually delete optimistic items. The handler just needs to write the server response to the synced data store, and when it completes, the optimistic state is automatically replaced. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/collections/query-collection.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/collections/query-collection.md b/docs/collections/query-collection.md index cdfe19333..cd4e7eedc 100644 --- a/docs/collections/query-collection.md +++ b/docs/collections/query-collection.md @@ -240,19 +240,16 @@ const todosCollection = createCollection( // Send to server and get back items with server-computed fields const serverItems = await api.createTodos(newItems) - // Update the optimistic items with server-computed fields - // (like server-generated IDs, timestamps, etc.) + // Sync server-computed fields (like server-generated IDs, timestamps, etc.) + // to the collection's synced data store todosCollection.utils.writeBatch(() => { - transaction.mutations.forEach((mutation, index) => { - const serverItem = serverItems[index] - // Remove temporary optimistic item - todosCollection.utils.writeDelete(mutation.key) - // Insert with server data + serverItems.forEach(serverItem => { todosCollection.utils.writeInsert(serverItem) }) }) // Skip automatic refetch since we've already synced the server response + // (optimistic state is automatically replaced when handler completes) return { refetch: false } },