Jeffrey Hicks

Jeffrey Hicks

Platform Eng @R360

Introduction to TanStack DB and React Query

Video introducing TanStack DB as an evolution of React Query with notes on their different approaches to state management

By TanStack • Sep 1, 2025

Overview

React Query (TanStack Query) and TanStack DB are both part of the TanStack ecosystem, but serve distinctly different purposes in modern web application development. While React Query focuses on server state management through intelligent caching and synchronization, TanStack DB extends these capabilities with a full client-side database layer featuring reactive queries and differential dataflow.

What They Are

React Query (TanStack Query)

React Query is a server state management library that simplifies data fetching, caching, and synchronization in React applications. It provides:

  • Intelligent caching with automatic background refetching
  • Query invalidation and automatic updates
  • Optimistic updates with rollback capabilities
  • Pagination and infinite scroll support
  • Parallel and dependent queries
  • Cross-framework support (React, Vue, Solid, Svelte)

TanStack DB

TanStack DB is a reactive client-side database that extends TanStack Query with:

  • Normalized data collections stored in memory
  • Live queries with sub-millisecond updates via differential dataflow
  • Cross-collection joins and aggregations
  • Transactional mutations with optimistic updates
  • Type-safe schemas and query builders

Key Differences

FeatureReact QueryTanStack DB
Primary PurposeServer state managementClient-side reactive database
Data ModelDocument-based cachingNormalized relational collections
Query EngineSimple key-based lookupsDifferential dataflow with SQL-like queries
ReactivityCache invalidation triggers refetchLive queries update incrementally
PerformanceNetwork-boundSub-millisecond updates (0.7ms for 100k rows)
JoinsNot supportedFull support for complex joins
Data SourceAlways fetches from serverWorks with synced local data

Architecture Comparison

React Query Architecture

React Query acts as a caching proxy between your UI and backend:

// React Query pattern
const { data: todos } = useQuery({
  queryKey: ['todos'],
  queryFn: async () => {
    const response = await fetch('/api/todos')
    return response.json()
  }
})

// Manual filtering happens in component
const incompleteTodos = todos?.filter(todo => !todo.completed)

TanStack DB Architecture

TanStack DB provides a normalized store with reactive queries:

// TanStack DB pattern
const todoCollection = createCollection({
  queryKey: ['todos'],
  queryFn: async () => fetch('/api/todos'),
  getKey: (item) => item.id,
  onUpdate: updateMutationFn
})

// Live query with automatic updates
const { data: todos } = useLiveQuery((q) =>
  q.from({ todo: todoCollection })
    .where(({ todo }) => !todo.completed)
)

Performance Characteristics

React Query Performance

  • Network-dependent for data freshness
  • Background refetching to keep data updated
  • Smart caching reduces unnecessary requests
  • Performance limited by API response times

TanStack DB Performance

  • Sub-millisecond query updates even with 100k+ rows
  • Differential dataflow updates only changed parts
  • No network round-trips for filtered/joined data
  • Benchmarks show 0.7ms updates for sorted 100k collections

Use Cases

When to Use React Query

React Query excels when you need:

  • Simple server state management with caching
  • RESTful or GraphQL API integration
  • Automatic refetching and background updates
  • Minimal client-side data transformation
  • Document-based data without complex relationships

When to Use TanStack DB

TanStack DB is ideal for:

  • Complex client-side queries with joins and aggregations
  • Real-time collaborative applications
  • Offline-first or local-first architectures
  • Applications with heavy data filtering/sorting
  • Normalized relational data models

Advanced Features Comparison

Optimistic Updates

React Query requires manual implementation:

const mutation = useMutation({
  mutationFn: updateTodo,
  onMutate: async (newTodo) => {
    await queryClient.cancelQueries(['todos'])
    const previous = queryClient.getQueryData(['todos'])
    queryClient.setQueryData(['todos'], old => [...old, newTodo])
    return { previous }
  },
  onError: (err, newTodo, context) => {
    queryClient.setQueryData(['todos'], context.previous)
  }
})

TanStack DB provides built-in support:

const complete = (todo) => {
  // Instantly applies optimistic state
  todoCollection.update(todo.id, (draft) => {
    draft.completed = true
  })
}

Complex Queries

React Query requires client-side processing:

// Multiple queries and manual joining
const { data: users } = useQuery(['users'], fetchUsers)
const { data: posts } = useQuery(['posts'], fetchPosts)

const userPosts = posts?.map(post => ({
  ...post,
  userName: users?.find(u => u.id === post.userId)?.name
}))

TanStack DB supports SQL-like joins:

const { data: userPosts } = useLiveQuery((q) =>
  q.from({ post: postsCollection })
    .join(
      { user: usersCollection },
      ({ post, user }) => eq(user.id, post.userId),
      'inner'
    )
    .select(({ post, user }) => ({
      id: post.id,
      title: post.title,
      userName: user.name
    }))
)

Integration and Migration

TanStack DB is designed to work alongside React Query. You can:

  1. Use both together - React Query for simple fetching, TanStack DB for complex queries
  2. Migrate incrementally - Adopt TanStack DB one collection at a time
  3. Share the QueryClient - Both libraries use the same underlying infrastructure
// Using both together
const queryClient = new QueryClient()

// React Query for simple data
const { data: profile } = useQuery(['profile'], fetchProfile)

// TanStack DB for complex relational data
const todosCollection = createCollection(
  queryCollectionOptions({
    queryKey: ['todos'],
    queryFn: fetchTodos,
    queryClient,
    getKey: (item) => item.id
  })
)

Key Announcement: TanStack DB as the Evolution Beyond React Query

TanStack DB has been announced as a drop-in replacement for React Query that represents Tanner Linsley’s original vision for TanStack Query from day one. According to the creator, React Query was a necessary stepping stone to get developers out of the “data fetching stone ages,” but TanStack DB addresses the final 10% of challenges that React Query couldn’t solve elegantly.

The Problem with React Query

While React Query revolutionized server state management, it has several limitations:

Manual Synchronization Challenges

  • Developers must manually build a pseudo sync engine to orchestrate between queries and mutations
  • Error-prone and repetitive process of defining dependencies between queries and mutations
  • Every mutation requires manual cache invalidation to keep UI data updated
  • Complex optimistic updates that are difficult to implement correctly

Architecture Limitations

  • Acts as a proxy between UI and backend, but still requires extensive manual coordination
  • Limited to simple caching strategies without true relational data capabilities
  • Performance bottlenecks when dealing with complex data transformations

TanStack DB Solution

Collection-Based Architecture

TanStack DB is built from the ground up based on collections, recognizing that most APIs are fundamentally CRUD operations on collections. Instead of defining separate queries and mutations, developers only need to define:

  • Create function
  • Read function
  • Update function
  • Delete function

Key Benefits

  • Live collections that automatically sync between frontend and backend
  • Built-in optimistic updates without manual implementation
  • In-memory database that orchestrates API calls automatically
  • Eliminates manual synchronization between queries and mutations
  • Global loading and error states instead of per-operation states

Code Implementation

The implementation is significantly simpler than React Query:

// Create collection with CRUD operations
const contactsCollection = createCollection({
  queryKey: ['contacts'],
  queryClient,
  queryFn: fetchContacts,
  getKey: (item) => item.id,
  onInsert: createContact,
  onUpdate: updateContact,
  onDelete: deleteContact
})

// Use live queries instead of separate queries
const { data: contacts } = useLiveQuery((q) => 
  q.from({ contacts: contactsCollection })
)

// Direct mutations without manual cache management
contactsCollection.delete(contactId) // Instantly reflected in UI

Performance and User Experience

Real-World Impact

The presenter migrated an existing contacts application from React Query to TanStack DB, demonstrating:

  • Immediate UI updates when creating, editing, or deleting contacts
  • Simplified codebase with less boilerplate
  • Better user experience with snappy, responsive interactions
  • Inversion of control where the library manages the sync process

Current Limitations

Two key features are missing that prevent immediate production adoption:

1. Pagination Support

  • No built-in partitioning for collections
  • Cannot handle endpoints that return paginated results
  • Pull requests are in progress to address this limitation

2. Global Selectors

  • Limited selector capabilities compared to React Query
  • No clear way to implement global selectors (e.g., getting count of items)
  • May exist in documentation but wasn’t immediately apparent

TanStack DB Partitions: Company-Scoped Collections

What Are Partitions?

Partitions in TanStack DB refer to collections that are scoped or filtered based on dynamic parameters like company ID, tenant ID, user ID, or any other contextual identifier. This pattern is essential for multi-tenant applications where you want collections that automatically filter data based on the current context rather than loading all data and filtering client-side.

Current Status: Active Development

The TanStack DB team is actively working on official partitioned collections support. There are two key GitHub issues tracking this:

Issue #315: Partitioned Collections - Specifically addresses collections where you don’t want to download all data but instead partition it by parameters like company ID, project ID, or other scoping criteria.

Issue #343: Paginated/Infinite Collections - Focuses on lazily loading data into collections using infinite query patterns, which often works hand-in-hand with partitioning.

Community Patterns (Current Workarounds)

While waiting for official partitioned collection support, the community has developed several patterns:

Collection Factory Pattern

// Higher-order function that creates company-scoped collections
export function createCompanyCollectionFactory<TItem>(
  collectionType: string,
  baseFetchFn: (companyId: string) => Promise<TItem[]>
) {
  const cache = new Map<string, Collection<TItem>>()
  const timeouts = new Map<string, NodeJS.Timeout>()
  
  return (companyId: string): Collection<TItem> => {
    // Cancel cleanup timer if collection is being used again
    if (timeouts.has(companyId)) {
      clearTimeout(timeouts.get(companyId))
      timeouts.delete(companyId)
    }
    
    // Return cached collection if exists
    if (cache.has(companyId)) {
      return cache.get(companyId)!
    }
    
    // Create new company-scoped collection
    const newCollection = createCollection(
      queryCollectionOptions({
        queryKey: [collectionType, companyId],
        queryFn: () => baseFetchFn(companyId),
        getKey: (item) => item.id,
        gcTime: 5 * 60 * 1000, // 5 minute cleanup
      })
    )
    
    cache.set(companyId, newCollection)
    
    // Listen for collection cleanup to remove from factory cache
    const unsubscribe = newCollection.subscribe(() => {
      if (newCollection.status === 'cleaned-up') {
        cache.delete(companyId)
        timeouts.delete(companyId)
        unsubscribe()
      }
    })
    
    return newCollection
  }
}

Using the Factory for Company Contacts

// Create factory for company-scoped contacts
const getCompanyContactsCollection = createCompanyCollectionFactory(
  'contacts',
  (companyId) => fetch(`/api/companies/${companyId}/contacts`).then(r => r.json())
)

// Usage in components
const CompanyContacts = () => {
  const { currentCompanyId } = useCompanyContext()
  const contactsCollection = getCompanyContactsCollection(currentCompanyId)
  
  const { data: contacts } = useLiveQuery((q) =>
    q.from({ contact: contactsCollection })
     .orderBy(({ contact }) => contact.name)
  )
  
  return <ContactList contacts={contacts} />
}

Advantages of Partitioned Collections

Memory Efficiency

  • Automatic garbage collection when collections aren’t being used
  • Per-company caching prevents loading unnecessary data
  • Lifecycle management ensures collections are cleaned up after inactivity

Performance Benefits

  • Company switching is instant - no network calls if collection exists
  • Sub-millisecond queries within each partition
  • Reduced memory footprint compared to loading all data

Developer Experience

  • Type-safe company-scoped collections
  • Automatic cleanup prevents memory leaks
  • Consistent API across different partitioned collections

Design Philosophy

The TanStack DB team intentionally started with a “load everything and filter client-side” approach to avoid premature optimization. As one team member noted: “Well it’s just do one thing at a time 😆” - reflecting their methodical approach to building features that solve real-world problems without overcomplicating the initial API.

Future Official Support

Based on the GitHub discussions, official partitioned collections will likely include:

  • Built-in partitioning syntax in collection definitions
  • Automatic lifecycle management for partitioned collections
  • Integration with infinite loading for large partitioned datasets
  • Server-side filtering optimizations

When to Use Partitions

Use partitioned collections when:

  • Data is naturally scoped by tenant/company/user
  • Loading all data would be inefficient or inappropriate
  • Users frequently switch between different scopes
  • You need real-time updates within specific scopes

Stick with regular collections when:

  • Dataset is small enough to load entirely
  • Filtering needs are simple and infrequent
  • Cross-scope queries are common

The partition pattern is a fundamental concept in TanStack DB architecture for building efficient multi-tenant applications, and the community has already developed robust workarounds while waiting for official implementation.

Migration Path

TanStack DB offers a smooth migration path from React Query:

  • Can be implemented incrementally alongside existing React Query code
  • Similar API patterns make transition relatively straightforward
  • The presenter successfully migrated a complete application in a single commit

Conclusion

TanStack DB represents a fundamental shift in thinking about client-side data management, moving from manual synchronization to automated collection-based operations. While still in beta, it addresses the core limitations of React Query and provides a more elegant solution for modern full-stack applications.

React Query remains valuable for straightforward server state management, particularly for applications with simple data requirements that don’t need complex relational operations.

TanStack DB is the evolution for applications requiring:

  • Automatic synchronization between queries and mutations
  • Complex relational data with joins and aggregations
  • Built-in optimistic updates without manual implementation
  • Real-time collaborative features

The evolution from React Query to TanStack DB demonstrates how the ecosystem is maturing toward more sophisticated, developer-friendly solutions that reduce boilerplate while improving performance and user experience. TanStack DB fulfills the original vision of what TanStack Query was meant to be - a complete client-side data management solution that eliminates the manual orchestration burden developers face with traditional data fetching libraries.

References

  1. TanStack DB 0.1: The Embedded Client Database for TanStack Query
  2. TanStack Query v3 Documentation
  3. What is React Query?
  4. A Quick Overview of the Main Features of React Query
  5. React Query Guide
  6. React Query: A Deep Dive into the Advanced Features
  7. TanStack Query Latest Documentation
  8. Introduction to TanStack DB
  9. TanStack DB Documentation
  10. TanStack DB GitHub Repository
  11. TanStack DB Beta Announcement
  12. Local-First Sync with TanStack DB
  13. TanStack Query vs Redux: What’s the Difference
  14. TanStack DB Optimistic Action Reference
  15. TanStack DB Live Queries Guide
  16. TanStack Combo Discussion on Reddit
  17. React Query vs TanStack DB Video
  18. TanStack DB Official Website
  19. TanStack DB Query Collection Documentation
  20. TanStack DB NPM Package
  21. TanStack DB Query Collection Reference
  22. Hacker News Discussion on TanStack DB
  23. Understanding TanStack Query Reactivity
  24. Difference Between TanStack React Query and React Query
  25. TanStack Query v5 React Overview
  26. TanStack DB v0.0.3 on NPM

Related

#react