Read a collection reactively with useLiveQuery(), and mutate it optimistically.
useLiveQuery re-runs whenever the collection changes, so components stay in sync without manual cache invalidation. .insert(), .update(), and .delete() apply to the UI immediately and roll back if the server rejects the change.
import { useLiveQuery, eq } from '@tanstack/react-db'
// import the todos collection you defined
import { todos } from './collections'
const Todos = () => {
const { data, isLoading } = useLiveQuery(
(q) => q.from({ todo: todos }).where(({ todo }) => eq(todo.completed, false)),
[]
)
const insertTodo = (title: string) => {
todos.insert({ id: crypto.randomUUID(), title, completed: false })
}
const completeTodo = (id: string) => {
todos.update(id, (draft) => {
draft.completed = true
})
}
const deleteTodo = (id: string) => {
todos.delete(id)
}
if (isLoading) return <p>Loading...</p>
return (
<div>
<button onClick={() => insertTodo('Buy milk')}>Add todo</button>
<ul>
{data.map((todo) => (
<li key={todo.id}>
{todo.title}
<button onClick={() => completeTodo(todo.id)}>Complete</button>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</li>
))}
</ul>
</div>
)
}