SuperAPI AI Directory

tanstack-db-skills-tanstack-db-collections

General & other

View repository

Updated 10-16 00:02

About this skill

Collections

Collections are typed data stores that decouple data loading from data binding. They can be populated from REST APIs, sync engines, or local storage, then queried uniformly with live queries.

Collection Types

Type Package Use Case
QueryCollection @tanstack/query-db-collection REST APIs via TanStack Query
ElectricCollection @tanstack/electric-db-collection Real-time Postgres sync via Electric
PowerSyncCollection @tanstack/powersync-db-collection Offline-first with PowerSync
RxDBCollection @tanstack/rxdb-db-collection RxDB local persistence
TrailBaseCollection @tanstack/trailbase-db-collection TrailBase real-time backend
LocalStorageCollection @tanstack/db Browser localStorage persistence
LocalOnlyCollection @tanstack/db In-memory state (no persistence)

Common Patterns

QueryCollection (REST APIs)

import { createCollection } from '@tanstack/react-db'
import { queryCollectionOptions } from '@tanstack/query-db-collection'

const todoCollection = createCollection(
  queryCollectionOptions({
    queryKey: ['todos'],
    queryFn: async () => {
      const response = await fetch('/api/todos')
      return response.json()
    },
    getKey: (item) => item.id,
    schema: todoSchema, // Optional: Zod, Valibot, etc.

    onInsert: async ({ transaction }) => {
      await Promise.all(
        transaction.mutations.map((m) =>
          fetch('/api/todos', {
            method: 'POST',
            body: JSON.stringify(m.modified),
          }),
        ),
      )
    },

    onUpdate: async ({ transaction }) => {
      await Promise.all(
        transaction.mutations.map((m) =>
          fetch(`/api/todos/${m.original.id}`, {
            method: 'PUT',
            body: JSON.stringify(m.modified),
          }),
        ),
      )
    },

    onDelete: async ({ transaction }) => {
      await Promise.all(
        transaction.mutations.map((m) =>
          fetch(`/api/todos/${m.original.id}`, { method: 'DELETE' }),
        ),
      )
    },
  }),
)

Sync Modes

Control how data loads into collections:

const productsCollection = createCollection(
  queryCollectionOptions({
    queryKey: ['products'],
    queryFn: async (ctx) => {
      // Query predicates available in ctx.meta for on-demand mode
      const params = parseLoadSubsetOptions(ctx.meta?.loadSubsetOptions)
      return api.getProducts(params)
    },
    getKey: (item) => item.id,

    // Choose sync mode:
    syncMode: 'eager', // Default: Load all upfront (<10k rows)
    // syncMode: 'on-demand', // Load only what queries request (>50k rows)
    // syncMode: 'progressive', // Load subset first, sync full in background
  }),
)
Mode Behavior Best For
eager Load entire collection upfront <10k rows, mostly static data
on-demand Load only what queries request >50k rows, search interfaces, catalogs
progressive Load query subset, sync full in background Collaborative apps, instant first paint

ElectricCollection (Real-time Sync)

import { createCollection } from '@tanstack/react-db'
import { electricCollectionOptions } from '@tanstack/electric-db-collection'

const todoCollection = createCollection(
  electricCollectionOptions({
    id: 'todos',
    schema: todoSchema,
    getKey: (item) => item.id,

    shapeOptions: {
      url: '/api/todos', // Your Electric proxy
      params: { table: 'todos' },
    },

    onInsert: async ({ transaction }) => {
      const response = await api.todos.create(transaction.mutations[0].modified)
      return { txid: response.txid } // Return txid to wait for sync
    },

    onUpdate: async ({ transaction }) => {
      const { original, changes } = transaction.mutations[0]
      const response = await api.todos.update(original.id, changes)
      return { txid: response.txid }
    },
  }),
)

LocalStorageCollection

import {
  createCollection,
  localStorageCollectionOptions,
} from '@tanstack/react-db'

const settingsCollection = createCollection(
  localStorageCollectionOptions({
    id: 'user-settings',
    storageKey: 'app-settings',
    getKey: (item) => item.id,
    schema: settingsSchema,
  }),
)

// Data persists across sessions and syncs across tabs
settingsCollection.insert({ id: 'theme', value: 'dark' })

LocalOnlyCollection

import {
  createCollection,
  localOnlyCollectionOptions,
} from '@tanstack/react-db'

const uiStateCollection = createCollection(
  localOnlyCollectionOptions({
    id: 'ui-state',
    getKey: (item) => item.id,
  }),
)

// In-memory only, lost on refresh
uiStateCollection.insert({ id: 'sidebar', expanded: true })

Collection with Schema

import { z } from 'zod'

const todoSchema = z.object({
  id: z.string(),
  text: z.string().min(1),
  completed: z.boolean().default(false),
  created_at: z
    .union([z.string(), z.date()])
    .transform((val) => (typeof val === 'string' ? new Date(val) : val))
    .default(() => new Date()),
})

const todoCollection = createCollection(
  queryCollectionOptions({
    schema: todoSchema, // Validates inserts/updates, transforms types
    queryKey: ['todos'],
    queryFn: async () => api.todos.getAll(),
    getKey: (item) => item.id,
  }),
)

Using TanStack Query Client

import { QueryClient } from '@tanstack/react-query'

const queryClient = new QueryClient()

const todoCollection = createCollection(
  queryCollectionOptions({
    queryKey: ['todos'],
    queryFn: async () => api.todos.getAll(),
    getKey: (item) => item.id,
    queryClient, // Use your existing query client
  }),
)

Collection API

// Read operations
collection.get(key) // Get item by key
collection.has(key) // Check if key exists
collection.toArray // Get all items as array
collection.size // Number of items

// Write operations (trigger handlers)
collection.insert(item) // Insert item(s)
collection.update(key, fn) // Update item(s) with draft function
collection.delete(key) // Delete item(s)

// Utilities (collection-specific)
collection.utils.refetch() // QueryCollection: refetch from API
collection.utils.awaitTxId() // ElectricCollection: wait for txid
collection.utils.awaitMatch() // ElectricCollection: wait for custom match
collection.utils.acceptMutations() // LocalCollection: accept in manual tx

Configuration Options

interface CollectionOptions {
  id?: string // Unique identifier
  getKey: (item) => Key // Extract unique key from item
  schema?: StandardSchema // Validation schema (Zod, Valibot, etc.)

  // Persistence handlers
  onInsert?: MutationFn
  onUpdate?: MutationFn
  onDelete?: MutationFn

  // QueryCollection specific
  queryKey?: QueryKey
  queryFn?: QueryFn
  queryClient?: QueryClient
  syncMode?: 'eager' | 'on-demand' | 'progressive'

  // ElectricCollection specific
  shapeOptions?: ShapeStreamOptions
}

Detailed References

Reference When to Use
references/query-collection.md REST API integration, predicate push-down, delta
references/electric-collection.md Electric setup, txid matching, shapes
references/local-collections.md LocalStorage, LocalOnly, cross-tab sync
references/sync-modes.md Eager vs on-demand vs progressive tradeoffs
references/custom-collections.md Building your own collection type