Claude Code for React Query DevTools and Debugging Patterns — Claude Skills 360 Blog
Blog / Frontend / Claude Code for React Query DevTools and Debugging Patterns
Frontend

Claude Code for React Query DevTools and Debugging Patterns

Published: March 29, 2027
Read time: 7 min read
By: Claude Skills 360

TanStack Query DevTools provides real-time visibility into query cache state — ReactQueryDevtools renders a floating panel showing every active query, its status, data, and stale time. useQueryClient() gives programmatic access to the cache for manual invalidation, prefetching, and inspection in tests. queryClient.getQueryData(queryKey) reads cached data without triggering a fetch. queryClient.setQueryData manually updates cache entries. The logger option on QueryClient overrides console methods for structured logging. persistQueryClient with createSyncStoragePersister or createAsyncStoragePersister persists the cache to localStorage across page reloads. dehydrate(queryClient) snapshots the cache for SSR hydration debugging. queryClient.getQueryCache().findAll() lists all tracked queries. Claude Code generates React Query DevTools setup, cache debugging utilities, persisted query configuration, and structured error logging patterns.

CLAUDE.md for React Query Debugging

## TanStack Query Debug Stack
- Version: @tanstack/react-query >= 5.40, @tanstack/react-query-devtools >= 5.40
- DevTools: <ReactQueryDevtools initialIsOpen={false} buttonPosition="bottom-right" />
- Cache read: queryClient.getQueryData(["orders", { status }]) — no fetch
- Cache write: queryClient.setQueryData(key, updater) — instant cache update
- Invalidate: queryClient.invalidateQueries({ queryKey: ["orders"] }) — triggers refetch
- List all: queryClient.getQueryCache().findAll() — inspect active queries
- Persist: persistQueryClient({ queryClient, persister: createSyncStoragePersister({...}) })
- Logger: new QueryClient({ logger: { log, warn, error: customFn } })

DevTools Setup

// app/providers.tsx — QueryClient + DevTools
"use client"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { ReactQueryDevtools } from "@tanstack/react-query-devtools"
import { useState } from "react"

function makeQueryClient() {
  return new QueryClient({
    defaultOptions: {
      queries: {
        staleTime: 60_000,        // 1 minute
        gcTime: 5 * 60_000,       // 5 minutes
        retry: (failureCount, error) => {
          // Don't retry 4xx errors
          if (error instanceof Error && "status" in error) {
            const status = (error as any).status as number
            if (status >= 400 && status < 500) return false
          }
          return failureCount < 3
        },
        refetchOnWindowFocus: process.env.NODE_ENV === "production",
      },
      mutations: {
        // Log all mutation errors in dev
        onError: process.env.NODE_ENV === "development"
          ? (error, variables) => {
              console.error("[Mutation Error]", { error, variables })
            }
          : undefined,
      },
    },
  })
}

// Singleton on server, fresh per render on client (Next.js App Router pattern)
let browserQueryClient: QueryClient | undefined

function getQueryClient() {
  if (typeof window === "undefined") return makeQueryClient()
  if (!browserQueryClient) browserQueryClient = makeQueryClient()
  return browserQueryClient
}

export function QueryProviders({ children }: { children: React.ReactNode }) {
  const [queryClient] = useState(() => getQueryClient())

  return (
    <QueryClientProvider client={queryClient}>
      {children}
      {process.env.NODE_ENV !== "production" && (
        <ReactQueryDevtools
          initialIsOpen={false}
          buttonPosition="bottom-right"
          // Show panel position
          position="bottom"
        />
      )}
    </QueryClientProvider>
  )
}

Cache Inspection Utilities

// lib/query-debug.ts — cache inspection helpers
import { useQueryClient, type QueryKey } from "@tanstack/react-query"
import { useCallback, useEffect } from "react"

// Hook to log all active queries in development
export function useQueryDebugger() {
  const queryClient = useQueryClient()

  useEffect(() => {
    if (process.env.NODE_ENV !== "development") return

    const cache = queryClient.getQueryCache()

    const unsubscribe = cache.subscribe(event => {
      if (event.type === "updated") {
        const query = event.query
        console.log(`[Query] ${JSON.stringify(query.queryKey)} → ${query.state.status}`, {
          dataUpdatedAt: query.state.dataUpdatedAt
            ? new Date(query.state.dataUpdatedAt).toLocaleTimeString()
            : null,
          isStale: query.isStale(),
          fetchCount: query.state.fetchFailureCount,
        })
      }
    })

    return unsubscribe
  }, [queryClient])
}

// Get snapshot of all cached data — useful in browser console
export function useQuerySnapshot() {
  const queryClient = useQueryClient()

  return useCallback(() => {
    const queries = queryClient.getQueryCache().findAll()
    return queries.map(q => ({
      key: q.queryKey,
      status: q.state.status,
      dataSize: JSON.stringify(q.state.data ?? null).length,
      stale: q.isStale(),
      updatedAt: q.state.dataUpdatedAt
        ? new Date(q.state.dataUpdatedAt).toISOString()
        : null,
    }))
  }, [queryClient])
}

// Force-expire specific queries for testing
export function useQueryExpiry() {
  const queryClient = useQueryClient()

  return useCallback((queryKey: QueryKey) => {
    queryClient.setQueryDefaults(queryKey, { staleTime: 0 })
    queryClient.invalidateQueries({ queryKey })
  }, [queryClient])
}

Persisted Cache

// lib/query-persister.ts — persist cache across page reloads
import { persistQueryClient, removeOldestQuery } from "@tanstack/react-query-persist-client"
import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister"
import type { QueryClient } from "@tanstack/react-query"

export function setupQueryPersistence(queryClient: QueryClient) {
  if (typeof window === "undefined") return

  const persister = createSyncStoragePersister({
    storage: window.localStorage,
    key: "rq-cache",
    // Remove oldest queries when storage limit hit
    retry: removeOldestQuery,
    // Throttle writes to avoid excessive storage writes
    throttleTime: 1_000,
    // Custom serializer for Date objects
    serialize: data => JSON.stringify(data),
    deserialize: data => JSON.parse(data),
  })

  persistQueryClient({
    queryClient,
    persister,
    maxAge: 24 * 60 * 60 * 1_000,  // Discard cache older than 24 hours

    // Only persist specific query keys
    dehydrateOptions: {
      shouldDehydrateQuery: query => {
        const key = query.queryKey[0]
        // Only cache user data and public content — skip sensitive queries
        return ["products", "categories", "user-profile"].includes(key as string)
      },
    },
  })
}

Error Boundary with Query Errors

// components/QueryErrorBoundary.tsx — catch query errors
"use client"
import { useQueryClient } from "@tanstack/react-query"
import { Component, type ReactNode } from "react"

interface Props {
  children: ReactNode
  queryKey?: unknown[]
  fallback?: ReactNode
}

interface State { hasError: boolean }

export class QueryErrorBoundary extends Component<Props, State> {
  state: State = { hasError: false }

  static getDerivedStateFromError(): State {
    return { hasError: true }
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback ?? (
        <div className="p-4 border border-red-200 rounded-lg">
          <p className="text-red-600 text-sm font-medium">Failed to load data</p>
          <button
            onClick={() => this.setState({ hasError: false })}
            className="mt-2 text-sm text-primary hover:underline"
          >
            Retry
          </button>
        </div>
      )
    }
    return this.props.children
  }
}

// Programmatic error reset tied to query invalidation
export function useQueryErrorReset(queryKey: unknown[]) {
  const queryClient = useQueryClient()

  return {
    resetError: () => {
      queryClient.resetQueries({ queryKey, exact: true })
    },
    refetch: () => {
      queryClient.invalidateQueries({ queryKey, exact: true })
    },
  }
}

Query Status Monitoring

// components/dev/QueryMonitor.tsx — visible query status panel (dev only)
"use client"
import { useQueryClient } from "@tanstack/react-query"
import { useState, useEffect } from "react"

interface QuerySummary {
  key: string
  status: string
  stale: boolean
  count: number
}

export function QueryMonitor() {
  const queryClient = useQueryClient()
  const [queries, setQueries] = useState<QuerySummary[]>([])

  useEffect(() => {
    const update = () => {
      const all = queryClient.getQueryCache().findAll()
      const grouped = new Map<string, QuerySummary>()

      for (const q of all) {
        const key = JSON.stringify(q.queryKey)
        const existing = grouped.get(key)
        if (!existing || q.state.dataUpdatedAt > (existing as any).__updatedAt) {
          grouped.set(key, {
            key: key.slice(0, 40),
            status: q.state.status,
            stale: q.isStale(),
            count: (existing?.count ?? 0) + 1,
          })
        }
      }
      setQueries([...grouped.values()].slice(0, 10))
    }

    update()
    const unsub = queryClient.getQueryCache().subscribe(update)
    return unsub
  }, [queryClient])

  if (process.env.NODE_ENV === "production") return null

  return (
    <div className="fixed bottom-16 left-4 z-40 bg-black/90 text-white text-xs rounded-lg p-3 max-w-xs font-mono">
      <p className="font-bold mb-2 text-yellow-400">Query Cache ({queries.length})</p>
      {queries.map(q => (
        <div key={q.key} className="flex gap-2 items-center mb-1">
          <span className={`h-1.5 w-1.5 rounded-full flex-shrink-0 ${
            q.status === "success" ? "bg-green-400" :
            q.status === "error" ? "bg-red-400" :
            q.status === "pending" ? "bg-yellow-400" : "bg-gray-400"
          }`} />
          <span className="truncate opacity-70">{q.key}</span>
          {q.stale && <span className="text-orange-400 flex-shrink-0">stale</span>}
        </div>
      ))}
    </div>
  )
}

For the SWR DevTools alternative when using Vercel’s SWR library — swr-devtools provides a similar cache inspector panel for SWR’s key-based cache but with a narrower feature set than ReactQueryDevtools, see the SWR debugging guide. For the Apollo Client DevTools alternative when using GraphQL with Apollo Client — Apollo’s browser DevTools extension provides query inspector, cache explorer, and mutation log that integrates directly into browser DevTools rather than rendering in the page, see the GraphQL client debugging guide. The Claude Skills 360 bundle includes TanStack Query skill sets covering DevTools, cache inspection, and persistence. Start with the free tier to try query debugging generation.

Keep Reading

Frontend

Claude Code for Chart.js Advanced: Custom Plugins and Mixed Charts

Advanced Chart.js patterns with Claude Code — chart.register() for tree-shaking, mixed chart types combining bar and line, custom plugin API with beforeDraw and afterDatasetsDraw hooks, ScriptableContext for computed colors, ChartDataLabels plugin for value labels, chartjs-plugin-zoom for pan and zoom, custom gradient fills via ctx.createLinearGradient, ChartJS annotation plugin for threshold lines, streaming data with chartjs-plugin-streaming, and react-chartjs-2 with useRef and chart instance.

6 min read Jun 27, 2027
Frontend

Claude Code for Nivo: Rich SVG and Canvas Charts

Build rich data visualizations with Nivo and Claude Code — ResponsiveLine and ResponsiveBar for adaptive charts, ResponsiveHeatMap for matrix data, ResponsiveTreeMap for hierarchal data, ResponsiveSunburst for nested proportions, ResponsiveChord for relationship diagrams, ResponsiveCalendar for activity heat maps, ResponsiveNetwork for force graphs, NivoTheme for consistent styling, tooltip customization with sliceTooltip, and motion config for spring animations.

6 min read Jun 26, 2027
Frontend

Claude Code for Victory Charts: React Native and Web Charts

Build cross-platform charts with Victory and Claude Code — VictoryChart, VictoryLine, VictoryBar, and VictoryScatter for web and React Native, VictoryPie for donut charts, VictoryArea for stacked areas, VictoryAxis for custom axes, VictoryTooltip and VictoryVoronoiContainer for hover tooltips, VictoryBrushContainer for range selection, VictoryZoomContainer for pan and zoom, VictoryLegend for series labels, custom theme with VictoryTheme, and VictoryStack for grouped bars.

6 min read Jun 25, 2027

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free