| name | TanStack DevTools Patterns |
| description | TanStack DevTools patterns for debugging and development. Activates when setting up development tools, debugging queries, or configuring developer experience in React applications. |
| version | 1.0.0 |
TanStack DevTools Patterns
This skill covers TanStack DevTools setup and usage for debugging React applications.
React Query DevTools
Installation
bun add -D @tanstack/react-query-devtools
Basic Setup
import { QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { createQueryClient } from '@/lib/query-client'
const queryClient = createQueryClient()
function App() {
return (
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
)
}
Production Lazy Loading
import { lazy, Suspense } from 'react'
const ReactQueryDevtools = lazy(() =>
import('@tanstack/react-query-devtools').then((d) => ({
default: d.ReactQueryDevtools,
}))
)
function App() {
return (
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
{import.meta.env.DEV && (
<Suspense fallback={null}>
<ReactQueryDevtools />
</Suspense>
)}
</QueryClientProvider>
)
}
DevTools Configuration
<ReactQueryDevtools
initialIsOpen={false}
position="bottom-right"
buttonPosition="bottom-right"
toggleButtonProps={{
style: {
marginBottom: '4rem',
},
}}
/>
Embedded DevTools
import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools'
function AdminDashboard() {
const [isOpen, setIsOpen] = useState(false)
return (
<div>
<button onClick={() => setIsOpen(!isOpen)}>
Toggle Query Inspector
</button>
{isOpen && (
<div className="devtools-panel">
<ReactQueryDevtoolsPanel />
</div>
)}
</div>
)
}
Router DevTools
Installation
bun add -D @tanstack/router-devtools
Setup
import { TanStackRouterDevtools } from '@tanstack/router-devtools'
export const Route = createRootRouteWithContext<RouterContext>()({
component: () => (
<>
<Outlet />
{import.meta.env.DEV && <TanStackRouterDevtools position="bottom-left" />}
</>
),
})
Lazy Loading Router DevTools
import { lazy, Suspense } from 'react'
const TanStackRouterDevtools = import.meta.env.DEV
? lazy(() =>
import('@tanstack/router-devtools').then((d) => ({
default: d.TanStackRouterDevtools,
}))
)
: () => null
export const Route = createRootRouteWithContext<RouterContext>()({
component: () => (
<>
<Outlet />
<Suspense fallback={null}>
<TanStackRouterDevtools />
</Suspense>
</>
),
})
Combined DevTools Layout
import { lazy, Suspense } from 'react'
const ReactQueryDevtools = lazy(() =>
import('@tanstack/react-query-devtools').then((d) => ({
default: d.ReactQueryDevtools,
}))
)
const TanStackRouterDevtools = lazy(() =>
import('@tanstack/router-devtools').then((d) => ({
default: d.TanStackRouterDevtools,
}))
)
export const Route = createRootRouteWithContext<RouterContext>()({
component: () => (
<>
<Outlet />
{import.meta.env.DEV && (
<Suspense fallback={null}>
<ReactQueryDevtools position="bottom-right" />
<TanStackRouterDevtools position="bottom-left" />
</Suspense>
)}
</>
),
})
Query Logging
Development Logger
import { QueryClient } from '@tanstack/react-query'
export function createQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
},
},
...(import.meta.env.DEV && {
logger: {
log: console.log,
warn: console.warn,
error: console.error,
},
}),
})
}
Custom Query Logger
const queryClient = new QueryClient({
queryCache: new QueryCache({
onError: (error, query) => {
if (import.meta.env.DEV) {
console.error(`Query Error [${query.queryKey}]:`, error)
}
},
onSuccess: (data, query) => {
if (import.meta.env.DEV) {
console.log(`Query Success [${query.queryKey}]:`, data)
}
},
}),
mutationCache: new MutationCache({
onError: (error, variables, context, mutation) => {
if (import.meta.env.DEV) {
console.error(`Mutation Error:`, error, variables)
}
},
}),
})
Performance Monitoring
Query Timing
const queryClient = new QueryClient({
queryCache: new QueryCache({
onSuccess: (data, query) => {
if (import.meta.env.DEV) {
const timing = query.state.dataUpdatedAt - query.state.fetchFailureCount
console.log(`[${query.queryKey}] fetched in ${timing}ms`)
}
},
}),
})
React Profiler Integration
import { Profiler } from 'react'
function App() {
const onRender = (
id: string,
phase: 'mount' | 'update',
actualDuration: number
) => {
if (import.meta.env.DEV && actualDuration > 16) {
console.warn(`Slow render: ${id} (${phase}) - ${actualDuration.toFixed(2)}ms`)
}
}
return (
<Profiler id="App" onRender={onRender}>
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>
</Profiler>
)
}
Debug Utilities
Query State Inspector Hook
import { useQueryClient } from '@tanstack/react-query'
function useQueryInspector() {
const queryClient = useQueryClient()
return {
getQueryState: (queryKey: unknown[]) =>
queryClient.getQueryState(queryKey),
getQueriesData: () =>
queryClient.getQueriesData({ type: 'all' }),
invalidateAll: () =>
queryClient.invalidateQueries(),
clearAll: () =>
queryClient.clear(),
}
}
function DevPanel() {
const inspector = useQueryInspector()
return (
<div>
<button onClick={() => console.log(inspector.getQueriesData())}>
Log All Queries
</button>
<button onClick={inspector.invalidateAll}>
Invalidate All
</button>
<button onClick={inspector.clearAll}>
Clear Cache
</button>
</div>
)
}
Route State Inspector
import { useRouter, useMatches } from '@tanstack/react-router'
function RouteInspector() {
const router = useRouter()
const matches = useMatches()
if (!import.meta.env.DEV) return null
return (
<div className="fixed bottom-20 left-4 bg-black/90 text-white p-4 rounded text-xs">
<pre>
{JSON.stringify(
{
pathname: router.state.location.pathname,
search: router.state.location.search,
matches: matches.map((m) => m.routeId),
},
null,
2
)}
</pre>
</div>
)
}
Vite Configuration
import { defineConfig } from 'vite'
export default defineConfig({
define: {
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
},
build: {
rollupOptions: {
external: import.meta.env.PROD
? ['@tanstack/react-query-devtools', '@tanstack/router-devtools']
: [],
},
},
})
Conventions
- Lazy load devtools - Always lazy load to reduce bundle size
- Environment check - Only render in development
- Position wisely - Avoid overlapping with app UI
- Production strip - Ensure devtools are excluded from prod
- Custom logging - Add meaningful logging in development
Anti-Patterns
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
function App() {
return (
<>
<Main />
<ReactQueryDevtools /> {/* Always included! */}
</>
)
}
const ReactQueryDevtools = lazy(() =>
import('@tanstack/react-query-devtools').then((d) => ({
default: d.ReactQueryDevtools,
}))
)
function App() {
return (
<>
<Main />
{import.meta.env.DEV && (
<Suspense fallback={null}>
<ReactQueryDevtools />
</Suspense>
)}
</>
)
}
<ReactQueryDevtools />
<TanStackRouterDevtools />
<ReactQueryDevtools position="bottom-right" />
<TanStackRouterDevtools position="bottom-left" />