- Overview
- 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)
- Installation
- npm
- install
- @tanstack/react-db @tanstack/query-db-collection
- Core Concepts
- Collections
-
- Normalized data stores wrapping data sources (TanStack Query, Electric, etc.)
- Live Queries
-
- Reactive subscriptions with SQL-like query builder
- Optimistic Mutations
-
- Automatic instant UI updates with rollback on failure
- Differential Dataflow
- Only recomputes affected query results on changes
Collections
Creating a 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
)
)
)
}
,
}
)
)
Sync Modes
// 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'
,
/ ... /
}
)
)
Live Queries
Basic Query
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
} Query Builder API const { data } = useLiveQuery ( ( q ) => q . from ( { t : todoCollection } ) . where ( ( { t } ) => eq ( t . status , 'active' ) ) . orderBy ( ( { t } ) => t . createdAt , 'desc' ) . limit ( 10 ) ) Joins 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 ) ) ) Filter Operators 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 ) ) With Ordering and Limits 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 ) ) Optimistic Mutations Insert 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 No Manual Boilerplate 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 Query-Driven Sync (On-Demand) 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 Predicate Mapping 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 ( ) ) } Performance Operation Latency Single row update (100k sorted collection) ~0.7 ms Subsequent queries (after sync) <1 ms Join across collections Sub-millisecond Supported Collection Types Query Collection - TanStack Query integration Electric Collection - Electric SQL real-time sync TrailBase Collection - TrailBase backend RxDB Collection - RxDB integration PowerSync Collection - PowerSync sync LocalStorage Collection - Browser persistence LocalOnly Collection - In-memory only API Summary import { createCollection , useLiveQuery } from '@tanstack/react-db' import { queryCollectionOptions } from '@tanstack/query-db-collection' import { eq , lt , and , parseLoadSubsetOptions } from '@tanstack/db' Best Practices Define collections at module level - they're singletons Choose the right sync mode : eager (<10k), on-demand (>50k), progressive (collaborative) Use joins instead of view-specific APIs - load normalized collections once Let TanStack Query handle fetching - DB augments Query, doesn't replace it Use parseLoadSubsetOptions to map live query predicates to API params Rely on automatic optimistic updates - don't manually manage optimistic state Use schemas for runtime validation and TypeScript inference Leverage incremental computation - let the engine handle filtering vs manual .filter() Common Pitfalls Creating collections inside components (should be module-level) Trying to replace TanStack Query entirely (DB builds on top of it) Using manual .filter() in render instead of live query where clauses Not providing getKey for proper normalization Forgetting mutation handlers ( onInsert , onUpdate , onDelete ) for server sync
tanstack-db
安装
npx skills add https://github.com/tanstack-skills/tanstack-skills --skill tanstack-db