TanStack DB bietet umfassende Fehlerbehandlungsfunktionen, um eine robuste Datensynchronisation und Zustandsverwaltung zu gewährleisten. Diese Anleitung behandelt die integrierten Fehlerbehandlungsmechanismen und wie man effektiv mit ihnen arbeitet.
TanStack DB stellt benannte Fehlerklassen für eine bessere Fehlerbehandlung und Typsicherheit bereit. Alle Fehlerklassen können aus @tanstack/db (oder häufiger aus dem frameworkspezifischen Paket, z. B. @tanstack/react-db) importiert werden.
import {
SchemaValidationError,
CollectionInErrorStateError,
DuplicateKeyError,
MissingHandlerError,
TransactionError,
// ... and many more
} from "@tanstack/db"
import {
SchemaValidationError,
CollectionInErrorStateError,
DuplicateKeyError,
MissingHandlerError,
TransactionError,
// ... and many more
} from "@tanstack/db"
Wird ausgelöst, wenn Daten während Einfüge- oder Aktualisierungsvorgängen nicht mit dem Schema der Sammlung übereinstimmen
import { SchemaValidationError } from "@tanstack/db"
try {
todoCollection.insert({ text: 123 }) // Invalid type
} catch (error) {
if (error instanceof SchemaValidationError) {
console.log(error.type) // 'insert' or 'update'
console.log(error.issues) // Array of validation issues
// Example issue: { message: "Expected string, received number", path: ["text"] }
}
}
import { SchemaValidationError } from "@tanstack/db"
try {
todoCollection.insert({ text: 123 }) // Invalid type
} catch (error) {
if (error instanceof SchemaValidationError) {
console.log(error.type) // 'insert' or 'update'
console.log(error.issues) // Array of validation issues
// Example issue: { message: "Expected string, received number", path: ["text"] }
}
}
Der Fehler beinhaltet
Sammlungen verfolgen ihren Status und wechseln zwischen Zuständen
import { useLiveQuery } from "@tanstack/react-db"
const TodoList = () => {
const { data, status, isError, isLoading, isReady } = useLiveQuery(
(query) => query.from({ todos: todoCollection })
)
if (isError) {
return <div>Collection is in error state</div>
}
if (isLoading) {
return <div>Loading...</div>
}
return <div>{data?.map(todo => <div key={todo.id}>{todo.text}</div>)}</div>
}
import { useLiveQuery } from "@tanstack/react-db"
const TodoList = () => {
const { data, status, isError, isLoading, isReady } = useLiveQuery(
(query) => query.from({ todos: todoCollection })
)
if (isError) {
return <div>Collection is in error state</div>
}
if (isLoading) {
return <div>Loading...</div>
}
return <div>{data?.map(todo => <div key={todo.id}>{todo.text}</div>)}</div>
}
Sammlungsstatuswerte
Wenn Mutationen fehlschlagen, rollt TanStack DB optimistische Updates automatisch zurück
const todoCollection = createCollection({
id: "todos",
onInsert: async ({ transaction }) => {
const response = await fetch("/api/todos", {
method: "POST",
body: JSON.stringify(transaction.mutations[0].modified),
})
if (!response.ok) {
// Throwing an error will rollback the optimistic state
throw new Error(`HTTP Error: ${response.status}`)
}
return response.json()
},
})
// Usage - optimistic update will be rolled back if the mutation fails
try {
const tx = todoCollection.insert({
id: "1",
text: "New todo",
completed: false,
})
await tx.isPersisted.promise
} catch (error) {
// The optimistic update has been automatically rolled back
console.error("Failed to create todo:", error)
}
const todoCollection = createCollection({
id: "todos",
onInsert: async ({ transaction }) => {
const response = await fetch("/api/todos", {
method: "POST",
body: JSON.stringify(transaction.mutations[0].modified),
})
if (!response.ok) {
// Throwing an error will rollback the optimistic state
throw new Error(`HTTP Error: ${response.status}`)
}
return response.json()
},
})
// Usage - optimistic update will be rolled back if the mutation fails
try {
const tx = todoCollection.insert({
id: "1",
text: "New todo",
completed: false,
})
await tx.isPersisted.promise
} catch (error) {
// The optimistic update has been automatically rolled back
console.error("Failed to create todo:", error)
}
Transaktionen haben die folgenden Zustände
Greifen Sie auf Transaktionsfehlerinformationen aus Sammlungsoperationen zu
const todoCollection = createCollection({
id: "todos",
onUpdate: async ({ transaction }) => {
const response = await fetch(`/api/todos/${transaction.mutations[0].key}`, {
method: "PUT",
body: JSON.stringify(transaction.mutations[0].modified),
})
if (!response.ok) {
throw new Error(`Update failed: ${response.status}`)
}
},
})
try {
const tx = await todoCollection.update("todo-1", (draft) => {
draft.completed = true
})
await tx.isPersisted.promise
} catch (error) {
// Transaction has been rolled back
console.log(tx.state) // "failed"
console.log(tx.error) // { message: "Update failed: 500", error: Error }
}
const todoCollection = createCollection({
id: "todos",
onUpdate: async ({ transaction }) => {
const response = await fetch(`/api/todos/${transaction.mutations[0].key}`, {
method: "PUT",
body: JSON.stringify(transaction.mutations[0].modified),
})
if (!response.ok) {
throw new Error(`Update failed: ${response.status}`)
}
},
})
try {
const tx = await todoCollection.update("todo-1", (draft) => {
draft.completed = true
})
await tx.isPersisted.promise
} catch (error) {
// Transaction has been rolled back
console.log(tx.state) // "failed"
console.log(tx.error) // { message: "Update failed: 500", error: Error }
}
Oder mit manueller Transaktionserstellung
const tx = createTransaction({
mutationFn: async ({ transaction }) => {
throw new Error("API failed")
}
})
tx.mutate(() => {
collection.insert({ id: "1", text: "Item" })
})
try {
await tx.commit()
} catch (error) {
// Transaction has been rolled back
console.log(tx.state) // "failed"
console.log(tx.error) // { message: "API failed", error: Error }
}
const tx = createTransaction({
mutationFn: async ({ transaction }) => {
throw new Error("API failed")
}
})
tx.mutate(() => {
collection.insert({ id: "1", text: "Item" })
})
try {
await tx.commit()
} catch (error) {
// Transaction has been rolled back
console.log(tx.state) // "failed"
console.log(tx.error) // { message: "API failed", error: Error }
}
Sammlungen im Zustand error können keine Operationen ausführen und müssen manuell wiederhergestellt werden
import { CollectionInErrorStateError } from "@tanstack/db"
try {
todoCollection.insert(newTodo)
} catch (error) {
if (error instanceof CollectionInErrorStateError) {
// Collection needs to be cleaned up and restarted
await todoCollection.cleanup()
// Now retry the operation
todoCollection.insert(newTodo)
}
}
import { CollectionInErrorStateError } from "@tanstack/db"
try {
todoCollection.insert(newTodo)
} catch (error) {
if (error instanceof CollectionInErrorStateError) {
// Collection needs to be cleaned up and restarted
await todoCollection.cleanup()
// Now retry the operation
todoCollection.insert(newTodo)
}
}
Direkte Mutationen erfordern die Konfiguration von Handlern
const todoCollection = createCollection({
id: "todos",
getKey: (todo) => todo.id,
// Missing onInsert handler
})
// This will throw an error
todoCollection.insert(newTodo)
// Error: Collection.insert called directly (not within an explicit transaction) but no 'onInsert' handler is configured
const todoCollection = createCollection({
id: "todos",
getKey: (todo) => todo.id,
// Missing onInsert handler
})
// This will throw an error
todoCollection.insert(newTodo)
// Error: Collection.insert called directly (not within an explicit transaction) but no 'onInsert' handler is configured
Das Einfügen von Elementen mit vorhandenen Schlüsseln löst einen Fehler aus
import { DuplicateKeyError } from "@tanstack/db"
try {
todoCollection.insert({ id: "existing-id", text: "Todo" })
} catch (error) {
if (error instanceof DuplicateKeyError) {
console.log(`Duplicate key: ${error.message}`)
}
}
import { DuplicateKeyError } from "@tanstack/db"
try {
todoCollection.insert({ id: "existing-id", text: "Todo" })
} catch (error) {
if (error instanceof DuplicateKeyError) {
console.log(`Duplicate key: ${error.message}`)
}
}
Schema-Validierung muss synchron erfolgen
const todoCollection = createCollection({
id: "todos",
getKey: (todo) => todo.id,
schema: {
"~standard": {
validate: async (data) => { // Async validation not allowed
// ...
}
}
}
})
// Will throw: Schema validation must be synchronous
const todoCollection = createCollection({
id: "todos",
getKey: (todo) => todo.id,
schema: {
"~standard": {
validate: async (data) => { // Async validation not allowed
// ...
}
}
}
})
// Will throw: Schema validation must be synchronous
Abfragesammlungen behandeln Synchronisationsfehler intelligent und markieren die Sammlung auch im Fehlerfall als bereit, um Anwendungen nicht zu blockieren
import { queryCollectionOptions } from "@tanstack/query-db-collection"
const todoCollection = createCollection(
queryCollectionOptions({
queryKey: ["todos"],
queryFn: async () => {
const response = await fetch("/api/todos")
if (!response.ok) {
throw new Error(`Failed to fetch: ${response.status}`)
}
return response.json()
},
queryClient,
getKey: (item) => item.id,
schema: todoSchema,
// Standard TanStack Query error handling options
retry: 3,
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
})
)
import { queryCollectionOptions } from "@tanstack/query-db-collection"
const todoCollection = createCollection(
queryCollectionOptions({
queryKey: ["todos"],
queryFn: async () => {
const response = await fetch("/api/todos")
if (!response.ok) {
throw new Error(`Failed to fetch: ${response.status}`)
}
return response.json()
},
queryClient,
getKey: (item) => item.id,
schema: todoSchema,
// Standard TanStack Query error handling options
retry: 3,
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
})
)
Wenn Synchronisationsfehler auftreten
Synchronisationsfunktionen müssen ihre eigenen Fehler während Schreibvorgängen behandeln
const collection = createCollection({
id: "todos",
sync: {
sync: ({ begin, write, commit }) => {
begin()
try {
// Will throw if key already exists
write({ type: "insert", value: { id: "existing-id", text: "Todo" } })
} catch (error) {
// Error: Cannot insert document with key "existing-id" from sync because it already exists
}
commit()
}
}
})
const collection = createCollection({
id: "todos",
sync: {
sync: ({ begin, write, commit }) => {
begin()
try {
// Will throw if key already exists
write({ type: "insert", value: { id: "existing-id", text: "Todo" } })
} catch (error) {
// Error: Cannot insert document with key "existing-id" from sync because it already exists
}
commit()
}
}
})
Bereinigungsfehler werden isoliert, um den Bereinigungsprozess nicht zu blockieren
const collection = createCollection({
id: "todos",
sync: {
sync: ({ begin, commit }) => {
begin()
commit()
// Return a cleanup function
return () => {
// If this throws, the error is re-thrown in a microtask
// but cleanup continues successfully
throw new Error("Sync cleanup failed")
}
},
},
})
// Cleanup completes even if the sync cleanup function throws
await collection.cleanup() // Resolves successfully
// Error is re-thrown asynchronously via queueMicrotask
const collection = createCollection({
id: "todos",
sync: {
sync: ({ begin, commit }) => {
begin()
commit()
// Return a cleanup function
return () => {
// If this throws, the error is re-thrown in a microtask
// but cleanup continues successfully
throw new Error("Sync cleanup failed")
}
},
},
})
// Cleanup completes even if the sync cleanup function throws
await collection.cleanup() // Resolves successfully
// Error is re-thrown asynchronously via queueMicrotask
Bereinigen Sie Sammlungen in Fehlerzuständen
if (todoCollection.status === "error") {
// Cleanup will stop sync and reset the collection
await todoCollection.cleanup()
// Collection will automatically restart on next access
todoCollection.preload() // Or any other operation
}
if (todoCollection.status === "error") {
// Cleanup will stop sync and reset the collection
await todoCollection.cleanup()
// Collection will automatically restart on next access
todoCollection.preload() // Or any other operation
}
Sammlungen arbeiten auch bei fehlgeschlagener Synchronisation weiterhin mit zwischengespeicherten Daten
const TodoApp = () => {
const { data, isError } = useLiveQuery((query) =>
query.from({ todos: todoCollection })
)
return (
<div>
{isError && (
<div>Sync failed, but you can still view cached data</div>
)}
{data?.map(todo => <TodoItem key={todo.id} todo={todo} />)}
</div>
)
}
const TodoApp = () => {
const { data, isError } = useLiveQuery((query) =>
query.from({ todos: todoCollection })
)
return (
<div>
{isError && (
<div>Sync failed, but you can still view cached data</div>
)}
{data?.map(todo => <TodoItem key={todo.id} todo={todo} />)}
</div>
)
}
Wenn eine Transaktion fehlschlägt, werden konkurrierende Transaktionen automatisch zurückgerollt
const tx1 = createTransaction({ mutationFn: async () => {} })
const tx2 = createTransaction({ mutationFn: async () => {} })
tx1.mutate(() => collection.update("1", draft => { draft.value = "A" }))
tx2.mutate(() => collection.update("1", draft => { draft.value = "B" })) // Same item
// Rolling back tx1 will also rollback tx2 due to conflict
tx1.rollback() // tx2 is automatically rolled back
const tx1 = createTransaction({ mutationFn: async () => {} })
const tx2 = createTransaction({ mutationFn: async () => {} })
tx1.mutate(() => collection.update("1", draft => { draft.value = "A" }))
tx2.mutate(() => collection.update("1", draft => { draft.value = "B" })) // Same item
// Rolling back tx1 will also rollback tx2 due to conflict
tx1.rollback() // tx2 is automatically rolled back
Transaktionen validieren ihren Zustand vor Operationen
const tx = createTransaction({ mutationFn: async () => {} })
// Complete the transaction
await tx.commit()
// These will throw:
tx.mutate(() => {}) // Error: You can no longer call .mutate() as the transaction is no longer pending
tx.commit() // Error: You can no longer call .commit() as the transaction is no longer pending
tx.rollback() // Error: You can no longer call .rollback() as the transaction is already completed
const tx = createTransaction({ mutationFn: async () => {} })
// Complete the transaction
await tx.commit()
// These will throw:
tx.mutate(() => {}) // Error: You can no longer call .mutate() as the transaction is no longer pending
tx.commit() // Error: You can no longer call .commit() as the transaction is no longer pending
tx.rollback() // Error: You can no longer call .rollback() as the transaction is already completed
Verwenden Sie instanceof-Prüfungen - Verwenden Sie instanceof anstelle von String-Vergleichen für die Fehlerbehandlung
// ✅ Good - type-safe error handling
if (error instanceof SchemaValidationError) {
// Handle validation error
}
// ❌ Avoid - brittle string matching
if (error.message.includes("validation failed")) {
// Handle validation error
}
// ✅ Good - type-safe error handling
if (error instanceof SchemaValidationError) {
// Handle validation error
}
// ❌ Avoid - brittle string matching
if (error.message.includes("validation failed")) {
// Handle validation error
}
Importieren Sie spezifische Fehlertypen - Importieren Sie nur die Fehlerklassen, die Sie benötigen, für eine bessere Tree-Shaking-Unterstützung
Behandeln Sie SchemaValidationError immer - Stellen Sie klares Feedback für Validierungsfehler bereit
Überprüfen Sie den Sammlungsstatus - Verwenden Sie die Flags isError, isLoading, isReady in React-Komponenten
Behandeln Sie Transaktionsversprechen - Behandeln Sie immer die Ablehnungen von isPersisted.promise
import {
createCollection,
SchemaValidationError,
DuplicateKeyError,
createTransaction
} from "@tanstack/db"
import { useLiveQuery } from "@tanstack/react-db"
const todoCollection = createCollection({
id: "todos",
schema: todoSchema,
getKey: (todo) => todo.id,
onInsert: async ({ transaction }) => {
const response = await fetch("/api/todos", {
method: "POST",
body: JSON.stringify(transaction.mutations[0].modified),
})
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
return response.json()
},
sync: {
sync: ({ begin, write, commit }) => {
// Your sync implementation
begin()
// ... sync logic
commit()
}
}
})
const TodoApp = () => {
const { data, status, isError, isLoading } = useLiveQuery(
(query) => query.from({ todos: todoCollection })
)
const handleAddTodo = async (text: string) => {
try {
const tx = await todoCollection.insert({
id: crypto.randomUUID(),
text,
completed: false,
})
// Wait for persistence
await tx.isPersisted.promise
} catch (error) {
if (error instanceof SchemaValidationError) {
alert(`Validation error: ${error.issues[0]?.message}`)
} else if (error instanceof DuplicateKeyError) {
alert("A todo with this ID already exists")
} else {
alert(`Failed to add todo: ${error.message}`)
}
}
}
const handleCleanup = async () => {
try {
await todoCollection.cleanup()
// Collection will restart on next access
} catch (error) {
console.error("Cleanup failed:", error)
}
}
if (isError) {
return (
<div>
<div>Collection error - data may be stale</div>
<button onClick={handleCleanup}>
Restart Collection
</button>
</div>
)
}
if (isLoading) {
return <div>Loading todos...</div>
}
return (
<div>
<button onClick={() => handleAddTodo("New todo")}>
Add Todo
</button>
{data?.map(todo => (
<div key={todo.id}>{todo.text}</div>
))}
</div>
)
}
import {
createCollection,
SchemaValidationError,
DuplicateKeyError,
createTransaction
} from "@tanstack/db"
import { useLiveQuery } from "@tanstack/react-db"
const todoCollection = createCollection({
id: "todos",
schema: todoSchema,
getKey: (todo) => todo.id,
onInsert: async ({ transaction }) => {
const response = await fetch("/api/todos", {
method: "POST",
body: JSON.stringify(transaction.mutations[0].modified),
})
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
return response.json()
},
sync: {
sync: ({ begin, write, commit }) => {
// Your sync implementation
begin()
// ... sync logic
commit()
}
}
})
const TodoApp = () => {
const { data, status, isError, isLoading } = useLiveQuery(
(query) => query.from({ todos: todoCollection })
)
const handleAddTodo = async (text: string) => {
try {
const tx = await todoCollection.insert({
id: crypto.randomUUID(),
text,
completed: false,
})
// Wait for persistence
await tx.isPersisted.promise
} catch (error) {
if (error instanceof SchemaValidationError) {
alert(`Validation error: ${error.issues[0]?.message}`)
} else if (error instanceof DuplicateKeyError) {
alert("A todo with this ID already exists")
} else {
alert(`Failed to add todo: ${error.message}`)
}
}
}
const handleCleanup = async () => {
try {
await todoCollection.cleanup()
// Collection will restart on next access
} catch (error) {
console.error("Cleanup failed:", error)
}
}
if (isError) {
return (
<div>
<div>Collection error - data may be stale</div>
<button onClick={handleCleanup}>
Restart Collection
</button>
</div>
)
}
if (isLoading) {
return <div>Loading todos...</div>
}
return (
<div>
<button onClick={() => handleAddTodo("New todo")}>
Add Todo
</button>
{data?.map(todo => (
<div key={todo.id}>{todo.text}</div>
))}
</div>
)
}
Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.