From 8429c17c26787568980ea1deba7017838e8afdf7 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Fri, 13 Nov 2026 11:10:47 -0700 Subject: [PATCH 1/7] docs(react-db): show how to animate row deletion --- docs/framework/react/overview.md | 70 ++++++++++++++++++++++ docs/guides/mutations.md | 2 + packages/react-db/skills/react-db/SKILL.md | 11 ++++ 3 files changed, 83 insertions(+) diff --git a/docs/framework/react/overview.md b/docs/framework/react/overview.md index f57c8f1564..4f5fd35902 100644 --- a/docs/framework/react/overview.md +++ b/docs/framework/react/overview.md @@ -237,3 +237,73 @@ When a changed query plan creates a new live-query Collection, `useLiveSuspenseQuery` shows your Suspense fallback until its initial result is ready. Finite outer-window moves retain the Collection and follow its window publication policy. + +### Animating row deletion with View Transitions + +To animate a row leaving a live query list, pass the query's `data` to +`useDeferredValue` and render the deferred rows in keyed `` +boundaries. `` is stable in React 19.3 and uses the browser's +View Transition API. [React View Transitions documentation](https://react.dev/reference/react/ViewTransition) + +`useLiveQuery` and `useLiveSuspenseQuery` subscribe through `useSyncExternalStore`. +React processes these updates synchronously, so wrapping `collection.delete()` +in `startTransition` doesn't trigger an exit animation. Call `delete()` directly. +React keeps the previous list visible until the deferred render removes the row +and can animate its exit. [React external store caveats](https://react.dev/reference/react/useSyncExternalStore#caveats) + +```tsx +import { + collectionOptions, + localOnlyCollectionOptions, + useDbClient, + useLiveSuspenseQuery, +} from '@tanstack/react-db' +import { useDeferredValue, ViewTransition } from 'react' + +const todoCollection = collectionOptions( + localOnlyCollectionOptions({ + id: 'todos', + getKey: (todo) => todo.id, + initialData: [ + { id: 1, text: 'Write a note' }, + { id: 2, text: 'Water the plants' }, + ], + }), +) + +export function TodoList() { + const todos = useDbClient().collection(todoCollection) + const { data } = useLiveSuspenseQuery({ + query: (q) => q.from({ todo: todoCollection }), + }) + const visibleTodos = useDeferredValue(data) + + return ( +
    + {visibleTodos.map((todo) => ( + +
  • + {todo.text}{' '} + +
  • +
    + ))} +
+ ) +} +``` + +Render `TodoList` inside your `DbProvider` and a `Suspense` boundary. The +deferred list can briefly show a row after the collection has deleted it. +Checking `todos.has(todo.id)` lets the handler ignore a second click during +that window. Other changes to the query results, such as inserts, can also +animate through these boundaries. diff --git a/docs/guides/mutations.md b/docs/guides/mutations.md index ba6f5d6e5c..73f0918697 100644 --- a/docs/guides/mutations.md +++ b/docs/guides/mutations.md @@ -445,6 +445,8 @@ todoCollection.delete(todo.id, { optimistic: false }) **Returns**: A `Transaction` object that you can use to track the mutation's lifecycle. +For an exit animation in React, see [Animating row deletion with View Transitions](../framework/react/overview.md#animating-row-deletion-with-view-transitions). + ## Operation Handlers Operation handlers are functions you provide when creating a collection that handle persisting mutations to your backend. Each collection can define three optional handlers: `onInsert`, `onUpdate`, and `onDelete`. diff --git a/packages/react-db/skills/react-db/SKILL.md b/packages/react-db/skills/react-db/SKILL.md index 3d0113ff77..01c93fa7d4 100644 --- a/packages/react-db/skills/react-db/SKILL.md +++ b/packages/react-db/skills/react-db/SKILL.md @@ -331,6 +331,17 @@ const { data } = useLiveQuery({ See meta-framework/SKILL.md for full preloading patterns. +### Animating row deletion with View Transitions + +To animate a live query row leaving with React `` (stable in +React 19.3), render `useDeferredValue(data)` and wrap each rendered row in +``. Call `collection.delete(row.id)` directly. +Wrapping the delete in `startTransition` leaves the `useSyncExternalStore` +update synchronous. Deferring the rendered rows lets React animate the removal. +Check `collection.has(row.id)` before deleting from a deferred list because a +row can stay visible briefly after deletion. +See the [React adapter example](https://tanstack.com/db/latest/docs/framework/react/overview#animating-row-deletion-with-view-transitions). + ## Common Mistakes ### CRITICAL Using === instead of eq() in .where() From ecc541212ab5f40e4c1621e67002597cea9e211b Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Fri, 13 Nov 2026 11:11:42 -0700 Subject: [PATCH 2/7] chore: add changeset for React View Transition guidance --- .changeset/react-view-transition-deletion.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/react-view-transition-deletion.md diff --git a/.changeset/react-view-transition-deletion.md b/.changeset/react-view-transition-deletion.md new file mode 100644 index 0000000000..f6cfc11dcf --- /dev/null +++ b/.changeset/react-view-transition-deletion.md @@ -0,0 +1,5 @@ +--- +'@tanstack/react-db': patch +--- + +Document how to animate row deletion with React View Transitions in the React adapter guide and skill. From 5d9a63ecdd289658b2932f47606a11b1f70b2fe7 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Fri, 13 Nov 2026 11:20:18 -0700 Subject: [PATCH 3/7] docs(react-db): generalize live query animation guidance --- .../react-live-query-view-transitions.md | 5 ++ .changeset/react-view-transition-deletion.md | 5 -- docs/framework/react/overview.md | 70 ++++++------------- docs/guides/mutations.md | 2 +- packages/react-db/skills/react-db/SKILL.md | 21 +++--- 5 files changed, 40 insertions(+), 63 deletions(-) create mode 100644 .changeset/react-live-query-view-transitions.md delete mode 100644 .changeset/react-view-transition-deletion.md diff --git a/.changeset/react-live-query-view-transitions.md b/.changeset/react-live-query-view-transitions.md new file mode 100644 index 0000000000..01cee30ddd --- /dev/null +++ b/.changeset/react-live-query-view-transitions.md @@ -0,0 +1,5 @@ +--- +'@tanstack/react-db': patch +--- + +Document how to animate live query results with React View Transitions in the React adapter guide and skill. diff --git a/.changeset/react-view-transition-deletion.md b/.changeset/react-view-transition-deletion.md deleted file mode 100644 index f6cfc11dcf..0000000000 --- a/.changeset/react-view-transition-deletion.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@tanstack/react-db': patch ---- - -Document how to animate row deletion with React View Transitions in the React adapter guide and skill. diff --git a/docs/framework/react/overview.md b/docs/framework/react/overview.md index 4f5fd35902..3f427aac81 100644 --- a/docs/framework/react/overview.md +++ b/docs/framework/react/overview.md @@ -238,63 +238,41 @@ When a changed query plan creates a new live-query Collection, is ready. Finite outer-window moves retain the Collection and follow its window publication policy. -### Animating row deletion with View Transitions +### Animating live query results with View Transitions -To animate a row leaving a live query list, pass the query's `data` to -`useDeferredValue` and render the deferred rows in keyed `` -boundaries. `` is stable in React 19.3 and uses the browser's -View Transition API. [React View Transitions documentation](https://react.dev/reference/react/ViewTransition) +Use React `` to animate rows entering, leaving, or moving in a +live query list. Pass the query's `data` to `useDeferredValue` and wrap each +rendered row in a `` with a stable key. `` is +stable in React 19.3 and uses the browser's View Transition API. +[React View Transitions documentation](https://react.dev/reference/react/ViewTransition) `useLiveQuery` and `useLiveSuspenseQuery` subscribe through `useSyncExternalStore`. -React processes these updates synchronously, so wrapping `collection.delete()` -in `startTransition` doesn't trigger an exit animation. Call `delete()` directly. -React keeps the previous list visible until the deferred render removes the row -and can animate its exit. [React external store caveats](https://react.dev/reference/react/useSyncExternalStore#caveats) +React handles store updates synchronously, even when you call a collection +mutation inside `startTransition`. `useDeferredValue` lets React keep showing +the previous results and render the latest results in the background. React +can animate the change when that render commits. The same pattern works for +changes from local writes or incoming sync data. +[React external store caveats](https://react.dev/reference/react/useSyncExternalStore#caveats) · [React useDeferredValue documentation](https://react.dev/reference/react/useDeferredValue) ```tsx -import { - collectionOptions, - localOnlyCollectionOptions, - useDbClient, - useLiveSuspenseQuery, -} from '@tanstack/react-db' +import { useLiveQuery } from '@tanstack/react-db' import { useDeferredValue, ViewTransition } from 'react' - -const todoCollection = collectionOptions( - localOnlyCollectionOptions({ - id: 'todos', - getKey: (todo) => todo.id, - initialData: [ - { id: 1, text: 'Write a note' }, - { id: 2, text: 'Water the plants' }, - ], - }), -) +import { todoCollection } from './collections' export function TodoList() { - const todos = useDbClient().collection(todoCollection) - const { data } = useLiveSuspenseQuery({ - query: (q) => q.from({ todo: todoCollection }), + const { data, isLoading } = useLiveQuery({ + query: (q) => + q.from({ todo: todoCollection }).orderBy(({ todo }) => todo.text), }) const visibleTodos = useDeferredValue(data) + if (isLoading) return

Loading…

+ return (
    {visibleTodos.map((todo) => ( -
  • - {todo.text}{' '} - -
  • +
  • {todo.text}
  • ))}
@@ -302,8 +280,6 @@ export function TodoList() { } ``` -Render `TodoList` inside your `DbProvider` and a `Suspense` boundary. The -deferred list can briefly show a row after the collection has deleted it. -Checking `todos.has(todo.id)` lets the handler ignore a second click during -that window. Other changes to the query results, such as inserts, can also -animate through these boundaries. +A deferred list can briefly show a row that the collection has already +removed. If a row has a Delete button, check `collection.has(id)` before +calling `collection.delete(id)` so a second click can safely do nothing. diff --git a/docs/guides/mutations.md b/docs/guides/mutations.md index 73f0918697..a697727bb1 100644 --- a/docs/guides/mutations.md +++ b/docs/guides/mutations.md @@ -445,7 +445,7 @@ todoCollection.delete(todo.id, { optimistic: false }) **Returns**: A `Transaction` object that you can use to track the mutation's lifecycle. -For an exit animation in React, see [Animating row deletion with View Transitions](../framework/react/overview.md#animating-row-deletion-with-view-transitions). +For an exit animation in React, see [Animating live query results with View Transitions](../framework/react/overview.md#animating-live-query-results-with-view-transitions). ## Operation Handlers diff --git a/packages/react-db/skills/react-db/SKILL.md b/packages/react-db/skills/react-db/SKILL.md index 01c93fa7d4..8a8f0e8dd9 100644 --- a/packages/react-db/skills/react-db/SKILL.md +++ b/packages/react-db/skills/react-db/SKILL.md @@ -331,16 +331,17 @@ const { data } = useLiveQuery({ See meta-framework/SKILL.md for full preloading patterns. -### Animating row deletion with View Transitions - -To animate a live query row leaving with React `` (stable in -React 19.3), render `useDeferredValue(data)` and wrap each rendered row in -``. Call `collection.delete(row.id)` directly. -Wrapping the delete in `startTransition` leaves the `useSyncExternalStore` -update synchronous. Deferring the rendered rows lets React animate the removal. -Check `collection.has(row.id)` before deleting from a deferred list because a -row can stay visible briefly after deletion. -See the [React adapter example](https://tanstack.com/db/latest/docs/framework/react/overview#animating-row-deletion-with-view-transitions). +### Animating live query results with View Transitions + +To animate rows entering, leaving, or moving, render `useDeferredValue(data)` +from `useLiveQuery` or `useLiveSuspenseQuery`. Wrap each rendered row in +`` (stable in React 19.3). `startTransition` around +a collection mutation leaves the `useSyncExternalStore` update synchronous. +Deferring the rendered results lets React animate changes from local writes +and incoming sync data. If the list has a Delete button, check +`collection.has(row.id)` before deleting because a removed row can stay +visible briefly. +See the [React adapter example](https://tanstack.com/db/latest/docs/framework/react/overview#animating-live-query-results-with-view-transitions). ## Common Mistakes From ad011edaf2063ea9a5f1fce58ffa567bf149b239 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Fri, 13 Nov 2026 11:26:16 -0700 Subject: [PATCH 4/7] docs(react-db): cover result keys and reduced motion --- docs/framework/react/overview.md | 15 ++++++++++++--- packages/react-db/skills/react-db/SKILL.md | 8 +++++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/docs/framework/react/overview.md b/docs/framework/react/overview.md index 3f427aac81..c696583cb0 100644 --- a/docs/framework/react/overview.md +++ b/docs/framework/react/overview.md @@ -242,8 +242,11 @@ window publication policy. Use React `` to animate rows entering, leaving, or moving in a live query list. Pass the query's `data` to `useDeferredValue` and wrap each -rendered row in a `` with a stable key. `` is -stable in React 19.3 and uses the browser's View Transition API. +rendered row in a `` with a stable, unique key. Use the result +row's `$key`, which also identifies individual rows in a join. +[Live query virtual properties](../../guides/live-queries.md#virtual-properties) + +`` is stable in React 19.3 and uses the browser's View Transition API. [React View Transitions documentation](https://react.dev/reference/react/ViewTransition) `useLiveQuery` and `useLiveSuspenseQuery` subscribe through `useSyncExternalStore`. @@ -271,7 +274,7 @@ export function TodoList() { return (
    {visibleTodos.map((todo) => ( - +
  • {todo.text}
  • ))} @@ -280,6 +283,12 @@ export function TodoList() { } ``` +Respect the user's `prefers-reduced-motion` setting. To disable the row +animations, pass `default={prefersReducedMotion ? 'none' : 'auto'}` to each +``, where `prefersReducedMotion` is your app's current value +for that media query. +[React View Transition props](https://react.dev/reference/react/ViewTransition#view-transition-class) + A deferred list can briefly show a row that the collection has already removed. If a row has a Delete button, check `collection.has(id)` before calling `collection.delete(id)` so a second click can safely do nothing. diff --git a/packages/react-db/skills/react-db/SKILL.md b/packages/react-db/skills/react-db/SKILL.md index 8a8f0e8dd9..2ff98a1cfb 100644 --- a/packages/react-db/skills/react-db/SKILL.md +++ b/packages/react-db/skills/react-db/SKILL.md @@ -334,11 +334,13 @@ See meta-framework/SKILL.md for full preloading patterns. ### Animating live query results with View Transitions To animate rows entering, leaving, or moving, render `useDeferredValue(data)` -from `useLiveQuery` or `useLiveSuspenseQuery`. Wrap each rendered row in -`` (stable in React 19.3). `startTransition` around +from `useLiveQuery` or `useLiveSuspenseQuery`. Wrap each rendered row in a +`` (stable in React 19.3) with a stable, unique result key such +as `row.$key`. Joined results use composite keys by default. `startTransition` around a collection mutation leaves the `useSyncExternalStore` update synchronous. Deferring the rendered results lets React animate changes from local writes -and incoming sync data. If the list has a Delete button, check +and incoming sync data. Use `prefers-reduced-motion` to reduce or disable +the animations. If the list has a Delete button, check `collection.has(row.id)` before deleting because a removed row can stay visible briefly. See the [React adapter example](https://tanstack.com/db/latest/docs/framework/react/overview#animating-live-query-results-with-view-transitions). From 79d803d98663a08ffa255e1b1e89a76d2c0d99b5 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Fri, 13 Nov 2026 11:30:33 -0700 Subject: [PATCH 5/7] docs(react-db): clarify keys for joined row deletion --- docs/framework/react/overview.md | 8 +++++--- packages/react-db/skills/react-db/SKILL.md | 10 +++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/framework/react/overview.md b/docs/framework/react/overview.md index c696583cb0..9ca5307c09 100644 --- a/docs/framework/react/overview.md +++ b/docs/framework/react/overview.md @@ -289,6 +289,8 @@ animations, pass `default={prefersReducedMotion ? 'none' : 'auto'}` to each for that media query. [React View Transition props](https://react.dev/reference/react/ViewTransition#view-transition-class) -A deferred list can briefly show a row that the collection has already -removed. If a row has a Delete button, check `collection.has(id)` before -calling `collection.delete(id)` so a second click can safely do nothing. +A deferred list can briefly show a row after deletion. If a row has a Delete +button, use the target collection's key for the record you want to delete. +Check `collection.has(key)` before calling `collection.delete(key)`. For a +joined result, `$key` identifies the result row for rendering. Read the record +key from the source you intend to delete from. diff --git a/packages/react-db/skills/react-db/SKILL.md b/packages/react-db/skills/react-db/SKILL.md index 2ff98a1cfb..1968f4bb97 100644 --- a/packages/react-db/skills/react-db/SKILL.md +++ b/packages/react-db/skills/react-db/SKILL.md @@ -336,13 +336,13 @@ See meta-framework/SKILL.md for full preloading patterns. To animate rows entering, leaving, or moving, render `useDeferredValue(data)` from `useLiveQuery` or `useLiveSuspenseQuery`. Wrap each rendered row in a `` (stable in React 19.3) with a stable, unique result key such -as `row.$key`. Joined results use composite keys by default. `startTransition` around -a collection mutation leaves the `useSyncExternalStore` update synchronous. +as `row.$key`. Joined results use composite keys by default. Wrapping a collection +mutation in `startTransition` leaves the `useSyncExternalStore` update synchronous. Deferring the rendered results lets React animate changes from local writes and incoming sync data. Use `prefers-reduced-motion` to reduce or disable -the animations. If the list has a Delete button, check -`collection.has(row.id)` before deleting because a removed row can stay -visible briefly. +the animations. A deleted row can stay visible briefly. If it has a Delete +button, check `collection.has(key)` before deleting. Use the target +collection's record key for both calls. See the [React adapter example](https://tanstack.com/db/latest/docs/framework/react/overview#animating-live-query-results-with-view-transitions). ## Common Mistakes From 9667fac739538b23b528634defd10c69870fab1a Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Fri, 13 Nov 2026 11:34:29 -0700 Subject: [PATCH 6/7] docs(react-db): distinguish numeric and string React keys --- docs/framework/react/overview.md | 4 +++- packages/react-db/skills/react-db/SKILL.md | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/framework/react/overview.md b/docs/framework/react/overview.md index 9ca5307c09..b6fe15d665 100644 --- a/docs/framework/react/overview.md +++ b/docs/framework/react/overview.md @@ -243,7 +243,9 @@ window publication policy. Use React `` to animate rows entering, leaving, or moving in a live query list. Pass the query's `data` to `useDeferredValue` and wrap each rendered row in a `` with a stable, unique key. Use the result -row's `$key`, which also identifies individual rows in a join. +row's `$key`, which also identifies individual rows in a join. If result keys +mix strings and numbers, include the type in the React key. For example, +`` `${typeof row.$key}:${row.$key}` `` keeps `1` and `'1'` distinct. [Live query virtual properties](../../guides/live-queries.md#virtual-properties) `` is stable in React 19.3 and uses the browser's View Transition API. diff --git a/packages/react-db/skills/react-db/SKILL.md b/packages/react-db/skills/react-db/SKILL.md index 1968f4bb97..33639a1f94 100644 --- a/packages/react-db/skills/react-db/SKILL.md +++ b/packages/react-db/skills/react-db/SKILL.md @@ -336,8 +336,10 @@ See meta-framework/SKILL.md for full preloading patterns. To animate rows entering, leaving, or moving, render `useDeferredValue(data)` from `useLiveQuery` or `useLiveSuspenseQuery`. Wrap each rendered row in a `` (stable in React 19.3) with a stable, unique result key such -as `row.$key`. Joined results use composite keys by default. Wrapping a collection -mutation in `startTransition` leaves the `useSyncExternalStore` update synchronous. +as `row.$key`. Joined results use composite keys by default. When result keys +mix numbers and strings, use `` `${typeof row.$key}:${row.$key}` `` as the React +key. Wrapping a collection mutation in `startTransition` leaves the +`useSyncExternalStore` update synchronous. Deferring the rendered results lets React animate changes from local writes and incoming sync data. Use `prefers-reduced-motion` to reduce or disable the animations. A deleted row can stay visible briefly. If it has a Delete From aaa41467f74869bfb5eeafad57730b1f29ec8fb9 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Fri, 13 Nov 2026 11:40:22 -0700 Subject: [PATCH 7/7] docs(react-db): make the live query animation example self-contained --- docs/framework/react/overview.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/framework/react/overview.md b/docs/framework/react/overview.md index b6fe15d665..1525f08751 100644 --- a/docs/framework/react/overview.md +++ b/docs/framework/react/overview.md @@ -260,9 +260,23 @@ changes from local writes or incoming sync data. [React external store caveats](https://react.dev/reference/react/useSyncExternalStore#caveats) · [React useDeferredValue documentation](https://react.dev/reference/react/useDeferredValue) ```tsx -import { useLiveQuery } from '@tanstack/react-db' +import { + collectionOptions, + localOnlyCollectionOptions, + useLiveQuery, +} from '@tanstack/react-db' import { useDeferredValue, ViewTransition } from 'react' -import { todoCollection } from './collections' + +const todoCollection = collectionOptions( + localOnlyCollectionOptions({ + id: 'todos', + getKey: (todo) => todo.id, + initialData: [ + { id: 1, text: 'Write a note' }, + { id: 2, text: 'Water the plants' }, + ], + }), +) export function TodoList() { const { data, isLoading } = useLiveQuery({