Help us improve
Share bugs, ideas, or general feedback.
From tanstack-db
Provides reactive client-side database layer with normalized collections, live SQL-like queries, optimistic mutations, and TanStack Query integration for fast UI updates in React apps.
npx claudepluginhub tanstack-skills/tanstack-skills --plugin tanstack-dbHow this skill is triggered — by the user, by Claude, or both
Slash command
/tanstack-db:tanstack-dbThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
TanStack DB is a client-side embedded database layer built on differential dataflow. It maintains normalized collections, uses incremental computation for live queries, provides automatic optimistic mutations, and integrates with TanStack Query for data fetching. Sub-millisecond updates even with 100k+ rows.
Manages server state with automatic caching, background refetching, pagination, infinite scrolling, and optimistic updates for React, Vue, Svelte, Angular apps.
Builds headless data tables with TanStack Table v8 featuring server-side pagination, filtering, sorting, virtualization for Cloudflare Workers + D1 databases and TanStack Query integration.
Provides TanStack Query v5 reference for React data fetching, caching, server state management using useQuery/useMutation hooks, QueryClient setup, optimistic updates, Next.js SSR/hydration, testing, TypeScript, and advanced patterns.
Share bugs, ideas, or general feedback.
TanStack DB is a client-side embedded database layer built on differential dataflow. It maintains normalized collections, uses incremental computation for live queries, provides automatic optimistic mutations, and integrates with TanStack Query for data fetching. Sub-millisecond updates even with 100k+ rows.
Package: @tanstack/react-db
Query Integration: @tanstack/query-db-collection
Status: Beta (v0.5)
npm install @tanstack/react-db @tanstack/query-db-collection
import { createCollection } from '@tanstack/react-db'
import { queryCollectionOptions } from '@tanstack/query-db-collection'
const todoCollection = createCollection(
queryCollectionOptions({
queryKey: ['todos'],
queryFn: async () => api.todos.getAll(),
getKey: (item) => item.id,
schema: todoSchema,
onInsert: async ({ transaction }) => {
await Promise.all(
transaction.mutations.map((mutation) =>
api.todos.create(mutation.modified)
)
)
},
onUpdate: async ({ transaction }) => {
await Promise.all(
transaction.mutations.map((mutation) =>
api.todos.update(mutation.modified)
)
)
},
onDelete: async ({ transaction }) => {
await Promise.all(
transaction.mutations.map((mutation) =>
api.todos.delete(mutation.original.id)
)
)
},
})
)
// Eager (default): Load entire collection upfront. Best for <10k rows.
const smallCollection = createCollection(
queryCollectionOptions({ syncMode: 'eager', /* ... */ })
)
// On-Demand: Load only what queries request. Best for >50k rows, search.
const largeCollection = createCollection(
queryCollectionOptions({
syncMode: 'on-demand',
queryFn: async (ctx) => {
const params = parseLoadSubsetOptions(ctx.meta?.loadSubsetOptions)
return api.getProducts(params)
},
})
)
// Progressive: Load query subset immediately, full sync in background.
const collaborativeCollection = createCollection(
queryCollectionOptions({ syncMode: 'progressive', /* ... */ })
)
import { useLiveQuery } from '@tanstack/react-db'
import { eq } from '@tanstack/db'
function TodoList() {
const { data: todos } = useLiveQuery((query) =>
query
.from({ todos: todoCollection })
.where(({ todos }) => eq(todos.completed, false))
)
return <ul>{todos.map(todo => <li key={todo.id}>{todo.text}</li>)}</ul>
}
const { data } = useLiveQuery((q) =>
q
.from({ t: todoCollection })
.where(({ t }) => eq(t.status, 'active'))
.orderBy(({ t }) => t.createdAt, 'desc')
.limit(10)
)
const { data } = useLiveQuery((q) =>
q
.from({ t: todoCollection })
.innerJoin(
{ u: userCollection },
({ t, u }) => eq(t.userId, u.id)
)
.innerJoin(
{ p: projectCollection },
({ u, p }) => eq(u.projectId, p.id)
)
.where(({ p }) => eq(p.id, currentProject.id))
)
import { eq, lt, and } from '@tanstack/db'
// Equality
eq(field, value)
// Less than
lt(field, value)
// AND
and(eq(product.category, 'electronics'), lt(product.price, 100))
const { data } = useLiveQuery((q) =>
q
.from({ product: productsCollection })
.where(({ product }) =>
and(eq(product.category, 'electronics'), lt(product.price, 100))
)
.orderBy(({ product }) => product.price, 'asc')
.limit(10)
)
todoCollection.insert({
id: uuid(),
text: 'New todo',
completed: false,
})
// Immediately: updates all live queries referencing this collection
// Background: calls onInsert handler to sync with server
// On failure: automatic rollback
| Before (TanStack Query only) | After (TanStack DB) |
|---|---|
Manual onMutate for optimistic state | Automatic |
Manual onError rollback logic | Automatic |
| Per-mutation cache invalidation | All live queries update automatically |
Live queries automatically generate optimized network requests:
// This live query...
useLiveQuery((q) =>
q.from({ product: productsCollection })
.where(({ product }) => and(eq(product.category, 'electronics'), lt(product.price, 100)))
.orderBy(({ product }) => product.price, 'asc')
.limit(10)
)
// ...automatically generates:
// GET /api/products?category=electronics&price_lt=100&sort=price:asc&limit=10
queryFn: async (ctx) => {
const { filters, sorts, limit } = parseLoadSubsetOptions(ctx.meta?.loadSubsetOptions)
const params = new URLSearchParams()
filters.forEach(({ field, operator, value }) => {
if (operator === 'eq') params.set(field.join('.'), String(value))
else if (operator === 'lt') params.set(`${field.join('.')}_lt`, String(value))
})
if (limit) params.set('limit', String(limit))
return fetch(`/api/products?${params}`).then(r => r.json())
}
| Operation | Latency |
|---|---|
| Single row update (100k sorted collection) | ~0.7 ms |
| Subsequent queries (after sync) | <1 ms |
| Join across collections | Sub-millisecond |
import { createCollection, useLiveQuery } from '@tanstack/react-db'
import { queryCollectionOptions } from '@tanstack/query-db-collection'
import { eq, lt, and, parseLoadSubsetOptions } from '@tanstack/db'
eager (<10k), on-demand (>50k), progressive (collaborative)parseLoadSubsetOptions to map live query predicates to API params.filter().filter() in render instead of live query where clausesgetKey for proper normalizationonInsert, onUpdate, onDelete) for server sync