From 0ac89c73727be3250eb7d7a107ec17114bbfa440 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Thu, 26 Jun 2025 11:23:18 -0600 Subject: [PATCH 1/8] Add createOptimisticAction helper --- docs/overview.md | 144 +++++------ packages/db/src/index.ts | 1 + packages/db/src/optimistic-action.ts | 27 +++ packages/db/src/types.ts | 11 + packages/db/tests/optimistic-action.test.ts | 227 ++++++++++++++++++ .../react-db/src/useOptimisticMutation.ts | 15 -- packages/react-db/vite.config.ts | 1 - packages/vue-db/src/useOptimisticMutation.ts | 15 -- 8 files changed, 328 insertions(+), 113 deletions(-) create mode 100644 packages/db/src/optimistic-action.ts create mode 100644 packages/db/tests/optimistic-action.test.ts delete mode 100644 packages/react-db/src/useOptimisticMutation.ts delete mode 100644 packages/vue-db/src/useOptimisticMutation.ts diff --git a/docs/overview.md b/docs/overview.md index ac5e7b9e..98a62d0a 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -117,12 +117,13 @@ Mutations are based on a `Transaction` primitive. For simple state changes, directly mutating the collection and persisting with the operator handlers is enough. -But for more complex use cases, you can directly create custom mutators with `useOptimisticMutation`. This lets you do things such as do transactions with multiple mutations across multiple collections, do chained transactions w/ intermediate rollbacks, etc. +But for more complex use cases, you can directly create custom actions with `createOptimisticAction` or custom transactions with `createTransaction`. This lets you do things such as do transactions with multiple mutations across multiple collections, do chained transactions w/ intermediate rollbacks, etc. For example, in the following code, the mutationFn first sends the write to the server using `await api.todos.update(updatedTodo)` and then calls `await collection.refetch()` to trigger a re-fetch of the collection contents using TanStack Query. When this second await resolves, the collection is up-to-date with the latest changes and the optimistic state is safely discarded. ```ts -const updateTodo = useOptimisticMutation({ +const updateTodo = createOptimisticAction<{id: string}>({ + onMutate, mutationFn: async ({ transaction }) => { const { collection, modified: updatedTodo } = transaction.mutations[0] @@ -384,55 +385,83 @@ const mutationFn: MutationFn = async ({ transaction }) => { } ``` -#### `useOptimisticMutation` +#### `createOptimisticAction` -Use the `useOptimisticMutation` hook with your `mutationFn` to create a mutator that you can use to mutate data in your components: +Use `createOptimisticAction` with your `mutationFn` and `onMutate` functions to create an action that you can use to mutate data in your components in fully custom ways: ```tsx -import { useOptimisticMutation } from '@tanstack/react-db' +import { createOptimisticAction } from '@tanstack/react-db' + +// Create the `addTodo` action, passing in your `mutationFn` and `onMutate`. +const addTodo = createOptimisticAction({ + onMutate: (text) => { + // Instantly applies the local optimistic state. + todoCollection.insert({ + id: uuid(), + text, + completed: false + }) + }, + mutationFn: async (text) => { + // Persist the todo to your backend + const response = await fetch('/api/todos', { + method: 'POST', + body: JSON.stringify({ text, completed: false }), + }) + return response.json() + } +}) const Todo = () => { - // Create the `addTodo` mutator, passing in your `mutationFn`. - const addTodo = useOptimisticMutation({ mutationFn }) - const handleClick = () => { - // Triggers the mutationFn - addTodo.mutate(() => - // Instantly applies the local optimistic state. - todoCollection.insert({ - id: uuid(), - text: '🔥 Make app faster', - completed: false - }) - ) + // Triggers the onMutate and then the mutationFn + addTodo('🔥 Make app faster') } return