From 2d49de0f87a4af14ecacb446e53624508ef6a347 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Wed, 3 Sep 2025 07:08:21 -0600 Subject: [PATCH] docs: clarify that mutationFn must await server sync before returning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add important documentation explaining that createOptimisticAction's mutationFn must ensure server writes have synced back before returning, as optimistic state is dropped when the function returns. Updated examples to demonstrate using collection-specific helpers like utils.refetch() to ensure synchronization. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- docs/overview.md | 16 +++++++++++++--- packages/db/src/optimistic-action.ts | 13 ++++++++++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/docs/overview.md b/docs/overview.md index 0f99db210..4aacc577d 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -517,7 +517,11 @@ Transactional mutators allow you to batch and stage local changes across collect Mutators are created with a `mutationFn`. You can define a single, generic `mutationFn` for your whole app. Or you can define collection or mutation specific functions. -The `mutationFn` is responsible for handling the local changes and processing them, usually to send them to a server or database to be stored, e.g.: +The `mutationFn` is responsible for handling the local changes and processing them, usually to send them to a server or database to be stored. + +**Important:** Inside your `mutationFn`, you must ensure that your server writes have synced back before you return, as the optimistic state is dropped when you return from the mutation function. You generally use collection-specific helpers to do this, such as Query's `utils.refetch()`, direct write APIs, or Electric's `utils.awaitTxId()`. + +For example: ```tsx import type { MutationFn } from "@tanstack/react-db" @@ -556,13 +560,19 @@ const addTodo = createOptimisticAction({ completed: false, }) }, - mutationFn: async (text) => { + mutationFn: async (text, params) => { // Persist the todo to your backend const response = await fetch("/api/todos", { method: "POST", body: JSON.stringify({ text, completed: false }), }) - return response.json() + const result = await response.json() + + // IMPORTANT: Ensure server writes have synced back before returning + // This ensures the optimistic state can be safely discarded + await todoCollection.utils.refetch() + + return result }, }) diff --git a/packages/db/src/optimistic-action.ts b/packages/db/src/optimistic-action.ts index 83db572bb..09ad02a15 100644 --- a/packages/db/src/optimistic-action.ts +++ b/packages/db/src/optimistic-action.ts @@ -9,6 +9,11 @@ import type { CreateOptimisticActionsOptions, Transaction } from "./types" * The optimistic update is applied via the `onMutate` callback, and the server mutation * is executed via the `mutationFn`. * + * **Important:** Inside your `mutationFn`, you must ensure that your server writes have synced back + * before you return, as the optimistic state is dropped when you return from the mutation function. + * You generally use collection-specific helpers to do this, such as Query's `utils.refetch()`, + * direct write APIs, or Electric's `utils.awaitTxId()`. + * * @example * ```ts * const addTodo = createOptimisticAction({ @@ -26,7 +31,13 @@ import type { CreateOptimisticActionsOptions, Transaction } from "./types" * method: 'POST', * body: JSON.stringify({ text, completed: false }), * }) - * return response.json() + * const result = await response.json() + * + * // IMPORTANT: Ensure server writes have synced back before returning + * // This ensures the optimistic state can be safely discarded + * await todoCollection.utils.refetch() + * + * return result * } * }) *