Back to list
salmanrrana

tanstack-errors

by salmanrrana

A local-first kanban board designed from the ground up for AI-assisted development

0🍴 0📅 Jan 25, 2026

SKILL.md


name: TanStack Errors description: This skill should be used when the user asks about "React Query error handling", "error boundaries with useQuery", "throwOnError", "global error callbacks", "onError callback", "query error state", "mutation error handling", or needs guidance on error handling strategies and patterns in TanStack Query. version: 1.0.0

TanStack Query Error Handling Patterns

This skill provides guidance for error handling in TanStack Query, covering local vs global strategies, Error Boundaries, and best practices based on TKDodo's recommendations.

Three Approaches to Error Handling

1. Local Error State

Check the error property directly in components:

function TodoList() {
  const { data, error, isError } = useQuery(todoQueries.all())

  if (isError) {
    return <ErrorMessage error={error} />
  }

  return <ul>{data?.map(todo => <TodoItem key={todo.id} todo={todo} />)}</ul>
}

Pros: Simple, explicit, component controls its error UI Cons: Repetitive across many components

2. Error Boundaries

Propagate errors to Error Boundaries using throwOnError:

// Component throws errors to boundary
function TodoList() {
  const { data } = useQuery({
    ...todoQueries.all(),
    throwOnError: true, // Throws on error
  })

  return <ul>{data.map(todo => <TodoItem key={todo.id} todo={todo} />)}</ul>
}

// Parent catches with Error Boundary
<ErrorBoundary fallback={<ErrorPage />}>
  <TodoList />
</ErrorBoundary>

3. Global Error Callbacks

Handle errors at the QueryCache level:

const queryClient = new QueryClient({
  queryCache: new QueryCache({
    onError: (error, query) => {
      // Global error handling
      if (query.state.data !== undefined) {
        // Only show toast if we already have data (background refetch failed)
        toast.error(`Background update failed: ${error.message}`)
      }
    },
  }),
})

Choosing the Right Approach

ScenarioRecommended Approach
Critical page dataError Boundary
Background refetch failuresGlobal callbacks with toast
Form validation errorsLocal error state
404 errorsLocal error state or boundary
5xx server errorsError Boundary
Network errorsGlobal callbacks

Error Boundaries with throwOnError

Basic Usage

const { data } = useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  throwOnError: true,
})

Conditional Throwing

Only throw for specific error types:

const { data } = useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  throwOnError: (error) => {
    // Only throw for server errors (5xx)
    return error.response?.status >= 500
  },
})

This pattern allows:

  • 5xx errors → Error Boundary (page-level error)
  • 4xx errors → Local handling (validation, not found)

Creating an Error Boundary

import { QueryErrorResetBoundary } from '@tanstack/react-query'
import { ErrorBoundary } from 'react-error-boundary'

function App() {
  return (
    <QueryErrorResetBoundary>
      {({ reset }) => (
        <ErrorBoundary
          onReset={reset}
          fallbackRender={({ error, resetErrorBoundary }) => (
            <div>
              <h2>Something went wrong</h2>
              <p>{error.message}</p>
              <button onClick={resetErrorBoundary}>Try again</button>
            </div>
          )}
        >
          <Routes />
        </ErrorBoundary>
      )}
    </QueryErrorResetBoundary>
  )
}

QueryErrorResetBoundary ensures queries retry when the boundary resets.

Global Error Callbacks

The Problem with Per-Query onError

// BAD: onError fires per observer (per component using this query)
const { data } = useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  onError: (error) => {
    toast.error(error.message) // Shows multiple toasts!
  },
})

If three components use this query, you get three toast notifications.

The Solution: QueryCache Callbacks

const queryClient = new QueryClient({
  queryCache: new QueryCache({
    onError: (error, query) => {
      // Fires once per request, not per observer
      toast.error(`Error: ${error.message}`)
    },
  }),
})

Smarter Global Handling

Only show errors when they matter:

const queryClient = new QueryClient({
  queryCache: new QueryCache({
    onError: (error, query) => {
      // Only toast on background refetch failures
      // (user already sees data, update silently failed)
      if (query.state.data !== undefined) {
        toast.error(`Failed to refresh: ${error.message}`)
      }
      // Initial load failures handled by component/boundary
    },
  }),
  mutationCache: new MutationCache({
    onError: (error) => {
      // Always toast mutation errors
      toast.error(`Operation failed: ${error.message}`)
    },
  }),
})

Prerequisites: Making Errors Happen

Fetch API Doesn't Throw on HTTP Errors

// BAD: No error on 404 or 500
const fetchTodo = async (id: string) => {
  const response = await fetch(`/api/todos/${id}`)
  return response.json() // Silently returns error body as "data"
}

// GOOD: Check response.ok
const fetchTodo = async (id: string) => {
  const response = await fetch(`/api/todos/${id}`)

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`)
  }

  return response.json()
}

Axios Throws by Default

// Axios throws on non-2xx responses automatically
const fetchTodo = async (id: string) => {
  const { data } = await axios.get(`/api/todos/${id}`)
  return data
}

Don't Swallow Errors

// BAD: Error caught and swallowed
const fetchTodo = async (id: string) => {
  try {
    const response = await api.get(`/todos/${id}`)
    return response.data
  } catch (error) {
    console.error(error) // Logged but not re-thrown!
    // Returns undefined - query thinks it succeeded
  }
}

// GOOD: Re-throw after logging
const fetchTodo = async (id: string) => {
  try {
    const response = await api.get(`/todos/${id}`)
    return response.data
  } catch (error) {
    console.error(error)
    throw error // Let React Query handle it
  }
}

Mutation Error Handling

Local Handling with mutate Callbacks

const mutation = useMutation({
  mutationFn: createTodo,
})

mutation.mutate(newTodo, {
  onError: (error) => {
    // Show error in UI
    setFormError(error.message)
  },
})

Global Mutation Errors

const queryClient = new QueryClient({
  mutationCache: new MutationCache({
    onError: (error, variables, context, mutation) => {
      // Log all mutation errors
      logger.error('Mutation failed', { error, variables })

      // Show generic toast for unexpected errors
      if (!mutation.meta?.skipToast) {
        toast.error('Operation failed. Please try again.')
      }
    },
  }),
})

// Skip toast for specific mutations
useMutation({
  mutationFn: saveForm,
  meta: { skipToast: true }, // Handle errors locally instead
})

Pattern: Combining Approaches

Use multiple strategies together:

// 1. Global: Log and toast background failures
const queryClient = new QueryClient({
  queryCache: new QueryCache({
    onError: (error, query) => {
      logger.error('Query failed', { error, queryKey: query.queryKey })

      if (query.state.data !== undefined) {
        toast.error('Failed to refresh data')
      }
    },
  }),
})

// 2. Boundary: Catch critical page errors
function TodoPage() {
  return (
    <ErrorBoundary fallback={<TodoPageError />}>
      <TodoList />
    </ErrorBoundary>
  )
}

// 3. Local: Handle specific errors
function TodoList() {
  const { data, error, isError } = useQuery({
    ...todoQueries.all(),
    throwOnError: (error) => error.status >= 500, // Only throw 5xx
  })

  if (isError && error.status === 404) {
    return <EmptyState message="No todos found" />
  }

  return <ul>{data?.map(...)}</ul>
}

Quick Reference

Error TypeStrategy
Network failureGlobal toast + retry
5xx server errorError Boundary
404 Not FoundLocal state
400 ValidationLocal state (show form errors)
401 UnauthorizedGlobal redirect to login
Background refetch failGlobal toast only
Mutation failLocal state or global toast

Additional Resources

Reference Files

For detailed patterns and advanced techniques, consult:

  • references/retry-patterns.md - Retry strategies and configuration
  • tanstack-query - Core concepts, staleTime
  • tanstack-mutations - Mutation-specific error handling
  • tanstack-types - Typed error handling

Score

Total Score

60/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

Reviews

💬

Reviews coming soon