Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/react-live-query-view-transitions.md
Original file line number Diff line number Diff line change
@@ -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.
73 changes: 73 additions & 0 deletions docs/framework/react/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,3 +237,76 @@ 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 live query results with View Transitions

Use React `<ViewTransition>` 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 `<ViewTransition>` with a stable, unique key. Use the result
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)

`<ViewTransition>` 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 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,
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' },
],
}),
)

export function TodoList() {
const { data, isLoading } = useLiveQuery({
query: (q) =>
q.from({ todo: todoCollection }).orderBy(({ todo }) => todo.text),
})
const visibleTodos = useDeferredValue(data)

if (isLoading) return <p>Loading…</p>

return (
<ul>
{visibleTodos.map((todo) => (
<ViewTransition key={todo.$key}>
<li>{todo.text}</li>
</ViewTransition>
))}
</ul>
)
}
```

Respect the user's `prefers-reduced-motion` setting. To disable the row
animations, pass `default={prefersReducedMotion ? 'none' : 'auto'}` to each
`<ViewTransition>`, 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 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.
2 changes: 2 additions & 0 deletions docs/guides/mutations.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 live query results with View Transitions](../framework/react/overview.md#animating-live-query-results-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`.
Expand Down
16 changes: 16 additions & 0 deletions packages/react-db/skills/react-db/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,22 @@ const { data } = useLiveQuery({

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 a
`<ViewTransition>` (stable in React 19.3) with a stable, unique result key such
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
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

### CRITICAL Using === instead of eq() in .where()
Expand Down
Loading