Skip to main content

react-db

React bindings for TanStack DB. Prefer useLiveQuery({ query }) with derived structured query identity. Provide queryKey only for opaque functional query variants or very hot render paths. Dependency arrays are legacy and warn before 1.0 removal. useLiveSuspenseQuery for React Suspense with Error Boundaries (data always defined). useLiveInfiniteQuery for cursor-based pagination (pageSize, fetchNextPage, hasNextPage, isFetchingNextPage). usePacedMutations for debounced React state updates. Return shape: data, state, collection, status, isLoading, isReady, isLoadingError, isCrashed, isSyncError, health. Import from @tanstack/react-db (re-exports all of @tanstack/db).

Datos de origen

Repositorio
TanStack/db
Última actividad en el origen
28 de octubre de 2026 a las 02:19
Idioma detectado de SKILL.md
inglés
Estrellas
3964
Forks
278

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
react-db
description
React bindings for TanStack DB. Prefer useLiveQuery({ query }) with derived structured query identity. Provide queryKey only for opaque functional query variants or very hot render paths. Dependency arrays are legacy and warn before 1.0 removal. useLiveSuspenseQuery for React Suspense with Error Boundaries (data always defined). useLiveInfiniteQuery for cursor-based pagination (pageSize, fetchNextPage, hasNextPage, isFetchingNextPage). usePacedMutations for debounced React state updates. Return shape: data, state, collection, status, isLoading, isReady, isLoadingError, isCrashed, isSyncError, health. Import from @tanstack/react-db (re-exports all of @tanstack/db).
type
framework
library
db
framework
react
library_version
0.6.17
requires
["db-core"]
sources
["TanStack/db:docs/framework/react/overview.md","TanStack/db:docs/guides/live-queries.md","TanStack/db:packages/react-db/src/useLiveQuery.ts","TanStack/db:packages/react-db/src/useLiveInfiniteQuery.ts"]
This skill builds on db-core. Read it first for collection setup, query builder, and mutation patterns. # TanStack DB — React ## Setup ```tsx import { eq, not, useLiveQuery } from '@tanstack/react-db' function TodoList() { const { data: todos, isLoading } = useLiveQuery({ query: (q) => q .from({ todo: todoCollection }) .where(({ todo }) => not(todo.completed)) .orderBy(({ todo }) => todo.created_at, 'asc'), }) if (isLoading) return <div>Loading...</div> return ( <ul> {todos.map((todo) => ( <li key={todo.id}>{todo.text}</li> ))} </ul> ) } ``` `@tanstack/react-db` re-exports everything from `@tanstack/db`. In React projects, import everything from `@tanstack/react-db`. ## Hooks ### useLiveQuery ```tsx // Preferred config object with derived query identity const { data, state, collection, status, isLoading, isReady, isLoadingError, // the first load failed: nothing to show isCrashed, // status 'crashed': local storage broke isIdle, isCleanedUp, isSyncError, // data is shown, but a sync problem is in health health, // health tree: one node for the query and one per source } = useLiveQuery({ query: (q) => q .from({ todo: todoCollection }) .where(({ todo }) => eq(todo.userId, userId)), }) // Static query const { data } = useLiveQuery({ query: (q) => q.from({ todo: todoCollection }), gcTime: 60000, }) // Pre-created collection (from route loader) const { data } = useLiveQuery(preloadedTodos) // Conditional query — derived identity handles enabled/disabled transitions const { data, status } = useLiveQuery({ query: (q) => { if (!userId) return undefined return q .from({ todo: todoCollection }) .where(({ todo }) => eq(todo.userId, userId)) }, }) // When disabled: status='disabled', data=undefined ``` `isLoadingError` means the first load failed and there is nothing to show; offer `collection.reload()`. `isCrashed` means local storage broke; only `cleanup({ dependents: true })` and a fresh start recover. After the query is ready, sync problems never change `status`: read `isSyncError` or the `health` tree to show stale-data warnings. `isError` and node `phase` were removed. Many queries of one shape, such as one per list row, can share one partition. Pass `pool: pooledLiveQueries` (from `@tanstack/db`) in the config form: `useLiveQuery({ query, pool: pooledLiveQueries })`. Pooling applies to queries that filter one eager source with `eq(field, literal)`. It is off by default, and Suspense and `DbClient` queries never pool. ### useLiveSuspenseQuery ```tsx // data is ALWAYS defined — never undefined // Must wrap in <Suspense> and <ErrorBoundary> function TodoList() { const { data: todos } = useLiveSuspenseQuery({ query: (q) => q.from({ todo: todoCollection }), }) return ( <ul> {todos.map((t) => ( <li key={t.id}>{t.text}</li> ))} </ul> ) } // Structured captured values are part of the derived identity and re-suspend when changed const { data } = useLiveSuspenseQuery({ query: (q) => q .from({ todo: todoCollection }) .where(({ todo }) => eq(todo.category, category)), }) ``` ### useLiveInfiniteQuery ```tsx const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useLiveInfiniteQuery( (q) => q .from({ posts: postsCollection }) .where(({ posts }) => eq(posts.category, category)) .orderBy(({ posts }) => posts.createdAt, 'desc'), { pageSize: 20, }, ) // data is the flat array of all loaded pages // fetchNextPage() loads the next page // hasNextPage is true when more data is available ``` ### usePacedMutations ```tsx import { usePacedMutations, debounceStrategy } from "@tanstack/react-db" const mutate = usePacedMutations({ onMutate: (value: string) => { noteCollection.update(noteId, (draft) => { draft.content = value }) }, mutationFn: async ({ transaction }) => { await api.notes.update(noteId, transaction.mutations[0].changes) }, strategy: debounceStrategy({ wait: 500 }), }) // In handler: <textarea onChange={(e) => mutate(e.target.value)} /> ``` ## Includes (Hierarchical Data) When a query uses `include()` for a subquery in `select`, each child field is a live `Collection`. Subscribe to it with `useLiveQuery` in a subcomponent: ```tsx function ProjectList() { const { data: projects } = useLiveQuery({ query: (q) => q.from({ p: projectsCollection }).select(({ p }) => ({ id: p.id, name: p.name, issues: include( q .from({ i: issuesCollection }) .where(({ i }) => eq(i.projectId, p.id)) .select(({ i }) => ({ id: i.id, title: i.title })), ), })), }) return ( <ul> {projects.map((project) => ( <li key={project.id}> {project.name} <IssueList issuesCollection={project.issues} /> </li> ))} </ul> ) } // Child component subscribes to the child Collection function IssueList({ issuesCollection }) { const { data: issues } = useLiveQuery(issuesCollection) return ( <ul> {issues.map((issue) => ( <li key={issue.id}>{issue.title}</li> ))} </ul> ) } ``` Only the affected `IssueList` re-renders when an issue changes — the parent does not. With `toArray()`, child results are plain arrays and the parent re-renders on child changes: ```tsx import { toArray, eq } from '@tanstack/react-db' const { data: projects } = useLiveQuery({ query: (q) => q.from({ p: projectsCollection }).select(({ p }) => ({ id: p.id, name: p.name, issues: toArray( q .from({ i: issuesCollection }) .where(({ i }) => eq(i.projectId, p.id)) .select(({ i }) => ({ id: i.id, title: i.title })), ), })), }) // project.issues is Array<{ id: string; title: string }> — no subcomponent needed ``` See db-core/live-queries/SKILL.md for full includes rules (correlation conditions, nested includes, aggregates). ## Virtual Properties Live query results include computed, read-only virtual properties on every row: - `$synced`: `true` when no pending local optimistic write affects the row; `false` while one does. It does not prove backend confirmation. - `$origin`: `"local"` if the last confirmed change came from this client, otherwise `"remote"`. - `$key`: the row key for the result. - `$collectionId`: the source collection ID. These props are added automatically and can be used in `where`, `select`, and `orderBy` clauses. Do not persist them back to storage. ```tsx const { data } = useLiveQuery({ query: (q) => q .from({ todo: todoCollection }) .where(({ todo }) => eq(todo.$synced, false)), }) // Shows rows with pending local optimistic writes ``` ## React-Specific Patterns ### Query identity ```tsx // Structured captured values are included in the derived identity const { data } = useLiveQuery({ query: (q) => q .from({ todo: todoCollection }) .where(({ todo }) => and(eq(todo.userId, userId), eq(todo.status, filter)), ), }) // Static query const { data } = useLiveQuery({ query: (q) => q.from({ todo: todoCollection }), }) ``` Use `queryKey` only when DB cannot derive identity from structured IR, such as `.fn.where`, `.fn.select`, `.fn.having`, or as a deliberate performance escape hatch on a hot render path: ```tsx const { data } = useLiveQuery({ queryKey: [todoCollection.id, 'search', search], query: (q) => q.from({ todo: todoCollection }).fn.where(({ todo }) => { return fuzzyMatch(todo.title, search) }), }) ``` Unhashable IR without `queryKey` throws `UnhashableQueryIRError`, even with a dependency array. Slow or repeated derived identity work warns once and points to the same `queryKey` escape hatch. ### Suspense + Error Boundary ```tsx <ErrorBoundary fallback={<div>Error</div>}> <Suspense fallback={<div>Loading...</div>}> <TodoList /> </Suspense> </ErrorBoundary> ``` ### Router loader preloading ```tsx // In route loader: await todoCollection.preload() // In component — data available immediately: const { data } = useLiveQuery({ query: (q) => q.from({ todo: todoCollection }), }) ``` See meta-framework/SKILL.md for full preloading patterns. ## Common Mistakes ### CRITICAL Using === instead of eq() in .where() Wrong — throws `InvalidWhereExpressionError` at runtime: ```tsx const { data } = useLiveQuery({ query: (q) => q .from({ todo: todoCollection }) .where(({ todo }) => todo.completed === true), }) ``` Correct — use expression functions from `@tanstack/react-db`: ```tsx import { useLiveQuery, eq } from '@tanstack/react-db' const { data } = useLiveQuery({ query: (q) => q .from({ todo: todoCollection }) .where(({ todo }) => eq(todo.completed, true)), }) ``` JavaScript `===`, `!==`, `<`, and `>` evaluate immediately and return a boolean. Use expression functions such as `eq`, `gt`, `gte`, `lt`, `lte`, `and`, `or`, and `not` for structured `.where()` predicates. See db-core/live-queries/SKILL.md for the full operator reference. ### CRITICAL Assigning useLiveQuery result directly instead of destructuring Wrong — crashes with "map is not a function": ```tsx const todos = useLiveQuery({ query: (q) => q.from({ todo: todoCollection }), }) return todos.map((t) => <li>{t.text}</li>) // TypeError: todos.map is not a function ``` Correct — destructure `{ data }`: ```tsx const { data: todos } = useLiveQuery({ query: (q) => q.from({ todo: todoCollection }), }) return todos.map((t) => <li key={t.id}>{t.text}</li>) ``` `useLiveQuery` returns an object with `data`, `isLoading`, `status`, and other fields. For queries that can be disabled, use `{ data: todos = [] }` to handle `data` being `undefined`. ### CRITICAL Using opaque query logic without queryKey Wrong: ```tsx const { data } = useLiveQuery({ query: (q) => q.from({ todo: todoCollection }).fn.where(({ todo }) => { return fuzzyMatch(todo.title, search) }), }) ``` Correct: ```tsx const { data } = useLiveQuery({ queryKey: [todoCollection.id, 'search', search], query: (q) =>
Ver en GitHub
Este SKILL.md es muy grande, por eso SkillsMP muestra aqui solo la primera seccion. Ver en GitHub