From a6d2c4907e1b3942f6f3893bd83b8a44af8f9c4c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 3 Nov 2025 20:46:33 +0000 Subject: [PATCH 1/8] docs: Add callout about bypassing mutation system Users can completely bypass the TanStack DB mutation system and use their existing mutation logic by calling backend APIs directly and either waiting for sync or manually refetching collections. --- docs/guides/mutations.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/guides/mutations.md b/docs/guides/mutations.md index f81fea8e6..6a02a9942 100644 --- a/docs/guides/mutations.md +++ b/docs/guides/mutations.md @@ -35,6 +35,27 @@ This pattern extends the Redux/Flux unidirectional data flow beyond the client t With an instant inner loop of optimistic state, superseded in time by the slower outer loop of persisting to the server and syncing the updated server state back into the collection. +> [!TIP] +> **You Can Skip TanStack DB Mutations Entirely** +> +> If you already have mutation logic in an existing system or simply prefer not to use optimistic updates, you can **completely bypass** TanStack DB's mutation system. For many collection types (especially `QueryCollection`), you can: +> +> 1. Call your backend mutation API directly +> 2. Wait for sync systems to sync changes back, **OR** manually refetch the collection with `collection.utils.refetch()` +> +> ```tsx +> // Bypass TanStack DB mutations - use your existing mutation logic +> const handleUpdateTodo = async (todoId, changes) => { +> // Call your backend directly +> await api.todos.update(todoId, changes) +> +> // Refetch to get updated data +> await todoCollection.utils.refetch() +> } +> ``` +> +> This approach is perfectly valid! The mutation system is optional and designed for scenarios where you want optimistic updates and automatic state management. + ### Simplified Mutations vs Traditional Approaches TanStack DB's mutation system eliminates much of the boilerplate required for optimistic updates in traditional approaches. Compare the difference: From d2b2c9780d630e366b723e2ef2a2b8c111d289fd Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 3 Nov 2025 20:50:18 +0000 Subject: [PATCH 2/8] docs: Improve bypass mutations callout - Clarify this is about not rewriting existing logic, not avoiding optimistic updates - Add Electric's awaitTxId as a sync option - Show collection-specific approaches (QueryCollection refetch vs Electric awaitTxId) - Provide examples for both patterns --- docs/guides/mutations.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/docs/guides/mutations.md b/docs/guides/mutations.md index 6a02a9942..5b7fa05dc 100644 --- a/docs/guides/mutations.md +++ b/docs/guides/mutations.md @@ -38,23 +38,32 @@ With an instant inner loop of optimistic state, superseded in time by the slower > [!TIP] > **You Can Skip TanStack DB Mutations Entirely** > -> If you already have mutation logic in an existing system or simply prefer not to use optimistic updates, you can **completely bypass** TanStack DB's mutation system. For many collection types (especially `QueryCollection`), you can: +> If you already have mutation logic in an existing system and don't want to rewrite it, you can **completely bypass** TanStack DB's mutation system. For many collection types, you can: > -> 1. Call your backend mutation API directly -> 2. Wait for sync systems to sync changes back, **OR** manually refetch the collection with `collection.utils.refetch()` +> 1. Call your backend mutation API directly (using your existing logic) +> 2. Wait for changes to sync back: +> - **QueryCollection**: Manually refetch with `collection.utils.refetch()` +> - **ElectricCollection**: Use `collection.utils.awaitTxId(txid)` to wait for sync +> - **Other sync systems**: Wait for your sync mechanism to update the collection > > ```tsx > // Bypass TanStack DB mutations - use your existing mutation logic > const handleUpdateTodo = async (todoId, changes) => { -> // Call your backend directly +> // Call your backend directly with your existing logic > await api.todos.update(todoId, changes) > -> // Refetch to get updated data +> // Wait for sync back > await todoCollection.utils.refetch() > } +> +> // With Electric +> const handleUpdateTodo = async (todoId, changes) => { +> const { txid } = await api.todos.update(todoId, changes) +> await todoCollection.utils.awaitTxId(txid) +> } > ``` > -> This approach is perfectly valid! The mutation system is optional and designed for scenarios where you want optimistic updates and automatic state management. +> This approach is perfectly valid! The mutation system is optional and designed for when you want the built-in optimistic updates and automatic state management patterns. ### Simplified Mutations vs Traditional Approaches From ca247c83eba78a0a1b50ee3ff5dbbb3f8b9efa35 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 3 Nov 2025 21:02:30 +0000 Subject: [PATCH 3/8] docs: Move bypass mutations to Mutation Approaches section Present bypassing the mutation system as one of several valid approaches rather than an aggressive callout. Fits more naturally as the first option under Mutation Approaches. --- docs/guides/mutations.md | 60 ++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/docs/guides/mutations.md b/docs/guides/mutations.md index 5b7fa05dc..fe8cb8b8c 100644 --- a/docs/guides/mutations.md +++ b/docs/guides/mutations.md @@ -35,36 +35,6 @@ This pattern extends the Redux/Flux unidirectional data flow beyond the client t With an instant inner loop of optimistic state, superseded in time by the slower outer loop of persisting to the server and syncing the updated server state back into the collection. -> [!TIP] -> **You Can Skip TanStack DB Mutations Entirely** -> -> If you already have mutation logic in an existing system and don't want to rewrite it, you can **completely bypass** TanStack DB's mutation system. For many collection types, you can: -> -> 1. Call your backend mutation API directly (using your existing logic) -> 2. Wait for changes to sync back: -> - **QueryCollection**: Manually refetch with `collection.utils.refetch()` -> - **ElectricCollection**: Use `collection.utils.awaitTxId(txid)` to wait for sync -> - **Other sync systems**: Wait for your sync mechanism to update the collection -> -> ```tsx -> // Bypass TanStack DB mutations - use your existing mutation logic -> const handleUpdateTodo = async (todoId, changes) => { -> // Call your backend directly with your existing logic -> await api.todos.update(todoId, changes) -> -> // Wait for sync back -> await todoCollection.utils.refetch() -> } -> -> // With Electric -> const handleUpdateTodo = async (todoId, changes) => { -> const { txid } = await api.todos.update(todoId, changes) -> await todoCollection.utils.awaitTxId(txid) -> } -> ``` -> -> This approach is perfectly valid! The mutation system is optional and designed for when you want the built-in optimistic updates and automatic state management patterns. - ### Simplified Mutations vs Traditional Approaches TanStack DB's mutation system eliminates much of the boilerplate required for optimistic updates in traditional approaches. Compare the difference: @@ -140,6 +110,36 @@ The benefits: TanStack DB provides different approaches to mutations, each suited to different use cases: +### Bypass the Mutation System + +If you already have mutation logic in an existing system and don't want to rewrite it, you can **completely bypass** TanStack DB's mutation system and use your existing patterns. + +```tsx +// Call your backend directly with your existing logic +const handleUpdateTodo = async (todoId, changes) => { + await api.todos.update(todoId, changes) + + // Wait for sync back + await todoCollection.utils.refetch() +} + +// With Electric +const handleUpdateTodo = async (todoId, changes) => { + const { txid } = await api.todos.update(todoId, changes) + await todoCollection.utils.awaitTxId(txid) +} +``` + +Use this approach when: +- You have existing mutation logic you don't want to rewrite +- You're comfortable with your current mutation patterns +- You want to use TanStack DB only for queries and state management + +How to sync changes back: +- **QueryCollection**: Manually refetch with `collection.utils.refetch()` +- **ElectricCollection**: Use `collection.utils.awaitTxId(txid)` to wait for specific transactions +- **Other sync systems**: Wait for your sync mechanism to update the collection + ### Collection-Level Mutations Collection-level mutations (`insert`, `update`, `delete`) are designed for **direct state manipulation** of a single collection. These are the simplest way to make changes and work well for straightforward CRUD operations. From 1d12974b1365e347a4c8ad0ac80deb0510a76792 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 3 Nov 2025 21:03:52 +0000 Subject: [PATCH 4/8] docs: Move bypass mutations to last approach Present TanStack DB's mutation approaches first, with bypassing the mutation system as the final option for those who prefer their existing patterns. --- docs/guides/mutations.md | 61 ++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/docs/guides/mutations.md b/docs/guides/mutations.md index fe8cb8b8c..a5b5fc307 100644 --- a/docs/guides/mutations.md +++ b/docs/guides/mutations.md @@ -110,36 +110,6 @@ The benefits: TanStack DB provides different approaches to mutations, each suited to different use cases: -### Bypass the Mutation System - -If you already have mutation logic in an existing system and don't want to rewrite it, you can **completely bypass** TanStack DB's mutation system and use your existing patterns. - -```tsx -// Call your backend directly with your existing logic -const handleUpdateTodo = async (todoId, changes) => { - await api.todos.update(todoId, changes) - - // Wait for sync back - await todoCollection.utils.refetch() -} - -// With Electric -const handleUpdateTodo = async (todoId, changes) => { - const { txid } = await api.todos.update(todoId, changes) - await todoCollection.utils.awaitTxId(txid) -} -``` - -Use this approach when: -- You have existing mutation logic you don't want to rewrite -- You're comfortable with your current mutation patterns -- You want to use TanStack DB only for queries and state management - -How to sync changes back: -- **QueryCollection**: Manually refetch with `collection.utils.refetch()` -- **ElectricCollection**: Use `collection.utils.awaitTxId(txid)` to wait for specific transactions -- **Other sync systems**: Wait for your sync mechanism to update the collection - ### Collection-Level Mutations Collection-level mutations (`insert`, `update`, `delete`) are designed for **direct state manipulation** of a single collection. These are the simplest way to make changes and work well for straightforward CRUD operations. @@ -228,6 +198,37 @@ Custom actions provide the cleanest way to capture specific types of mutations a - **Collection-level mutations** (`collection.update`): Simple CRUD operations on a single collection - **`createOptimisticAction`**: Intent-based operations, multi-collection mutations, immediately committed - **`createTransaction`**: Fully custom transactions, delayed commits, multi-step workflows +- **Bypass the mutation system**: Use your existing mutation logic without rewriting + +### Bypass the Mutation System + +If you already have mutation logic in an existing system and don't want to rewrite it, you can **completely bypass** TanStack DB's mutation system and use your existing patterns. + +```tsx +// Call your backend directly with your existing logic +const handleUpdateTodo = async (todoId, changes) => { + await api.todos.update(todoId, changes) + + // Wait for sync back + await todoCollection.utils.refetch() +} + +// With Electric +const handleUpdateTodo = async (todoId, changes) => { + const { txid } = await api.todos.update(todoId, changes) + await todoCollection.utils.awaitTxId(txid) +} +``` + +Use this approach when: +- You have existing mutation logic you don't want to rewrite +- You're comfortable with your current mutation patterns +- You want to use TanStack DB only for queries and state management + +How to sync changes back: +- **QueryCollection**: Manually refetch with `collection.utils.refetch()` +- **ElectricCollection**: Use `collection.utils.awaitTxId(txid)` to wait for specific transactions +- **Other sync systems**: Wait for your sync mechanism to update the collection ## Mutation Lifecycle From 320091b7760e8b55b511beb2d4e0f3cac93c7351 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 3 Nov 2025 21:08:31 +0000 Subject: [PATCH 5/8] docs: Remove createTransaction from Mutation Approaches Manual transactions is niche and covered in detail later in its own section. Keep Mutation Approaches focused on the main patterns. --- docs/guides/mutations.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/guides/mutations.md b/docs/guides/mutations.md index a5b5fc307..cbdecda31 100644 --- a/docs/guides/mutations.md +++ b/docs/guides/mutations.md @@ -160,7 +160,7 @@ onUpdate: async ({ transaction }) => { ### Intent-Based Mutations with Custom Actions -For more complex scenarios, use `createOptimisticAction` or `createTransaction` to create **intent-based mutations** that capture specific user actions. +For more complex scenarios, use `createOptimisticAction` to create **intent-based mutations** that capture specific user actions. ```tsx // Intent: "like this post" @@ -197,7 +197,6 @@ Custom actions provide the cleanest way to capture specific types of mutations a - **Collection-level mutations** (`collection.update`): Simple CRUD operations on a single collection - **`createOptimisticAction`**: Intent-based operations, multi-collection mutations, immediately committed -- **`createTransaction`**: Fully custom transactions, delayed commits, multi-step workflows - **Bypass the mutation system**: Use your existing mutation logic without rewriting ### Bypass the Mutation System From e379d1d89dcb5c485aeed364944bf073af1d4286 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 3 Nov 2025 21:41:43 +0000 Subject: [PATCH 6/8] docs: Clarify why to await sync in bypass approach Explain that awaiting refetch/sync lets you know when the server change is loaded in the collection, so you can render new data, hide loading indicators, navigate, etc. --- docs/guides/mutations.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/guides/mutations.md b/docs/guides/mutations.md index cbdecda31..1a20c22c8 100644 --- a/docs/guides/mutations.md +++ b/docs/guides/mutations.md @@ -208,14 +208,20 @@ If you already have mutation logic in an existing system and don't want to rewri const handleUpdateTodo = async (todoId, changes) => { await api.todos.update(todoId, changes) - // Wait for sync back + // Wait for the server change to load into the collection await todoCollection.utils.refetch() + + // Now you know the new data is loaded and can render it or hide loaders } // With Electric const handleUpdateTodo = async (todoId, changes) => { const { txid } = await api.todos.update(todoId, changes) + + // Wait for this specific transaction to sync into the collection await todoCollection.utils.awaitTxId(txid) + + // Now the server change is loaded and you can update UI accordingly } ``` @@ -225,10 +231,12 @@ Use this approach when: - You want to use TanStack DB only for queries and state management How to sync changes back: -- **QueryCollection**: Manually refetch with `collection.utils.refetch()` -- **ElectricCollection**: Use `collection.utils.awaitTxId(txid)` to wait for specific transactions +- **QueryCollection**: Manually refetch with `collection.utils.refetch()` to reload data from the server +- **ElectricCollection**: Use `collection.utils.awaitTxId(txid)` to wait for a specific transaction to sync - **Other sync systems**: Wait for your sync mechanism to update the collection +After the sync completes, the collection will have the updated server data and you can render the new state, hide loading indicators, navigate to a new page, etc. + ## Mutation Lifecycle The mutation lifecycle follows a consistent pattern across all mutation types: From 4b5874e092255c7aa608352ee33c21ad0a9d5d9a Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 3 Nov 2025 21:43:18 +0000 Subject: [PATCH 7/8] docs: Add success messages to sync completion reasons Include showing success messages as a common reason to await sync completion, alongside rendering data, hiding loaders, and navigating. --- docs/guides/mutations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/mutations.md b/docs/guides/mutations.md index 1a20c22c8..85c665d0e 100644 --- a/docs/guides/mutations.md +++ b/docs/guides/mutations.md @@ -235,7 +235,7 @@ How to sync changes back: - **ElectricCollection**: Use `collection.utils.awaitTxId(txid)` to wait for a specific transaction to sync - **Other sync systems**: Wait for your sync mechanism to update the collection -After the sync completes, the collection will have the updated server data and you can render the new state, hide loading indicators, navigate to a new page, etc. +After the sync completes, the collection will have the updated server data and you can render the new state, hide loading indicators, show success messages, navigate to a new page, etc. ## Mutation Lifecycle From 84c09ad2b4503f57161b3f63ec390fdef4c6c664 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 3 Nov 2025 22:34:49 +0000 Subject: [PATCH 8/8] docs: Explain write-then-sync pattern in bypass approach Clarify that you write to the server like normal, then use your collection's mechanism to await the server write and know when to update UI. --- docs/guides/mutations.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/mutations.md b/docs/guides/mutations.md index 85c665d0e..323aac56e 100644 --- a/docs/guides/mutations.md +++ b/docs/guides/mutations.md @@ -203,6 +203,8 @@ Custom actions provide the cleanest way to capture specific types of mutations a If you already have mutation logic in an existing system and don't want to rewrite it, you can **completely bypass** TanStack DB's mutation system and use your existing patterns. +With this approach, you write to the server like normal using your existing logic, then use your collection's mechanism for refetching or syncing data to await the server write. After the sync completes, the collection will have the updated server data and you can render the new state, hide loading indicators, show success messages, navigate to a new page, etc. + ```tsx // Call your backend directly with your existing logic const handleUpdateTodo = async (todoId, changes) => { @@ -235,8 +237,6 @@ How to sync changes back: - **ElectricCollection**: Use `collection.utils.awaitTxId(txid)` to wait for a specific transaction to sync - **Other sync systems**: Wait for your sync mechanism to update the collection -After the sync completes, the collection will have the updated server data and you can render the new state, hide loading indicators, show success messages, navigate to a new page, etc. - ## Mutation Lifecycle The mutation lifecycle follows a consistent pattern across all mutation types: