skills/accelint-tanstack-query-best-practices/SKILL.md
Use when configuring QueryClient, implementing mutations, debugging performance, or adding optimistic updates with @tanstack/react-query in Next.js App Router. Covers factory patterns, query keys, cache invalidation, observer debugging, HydrationBoundary, multi-layer caching. Keywords TanStack Query, useSuspenseQuery, useQuery, useMutation, invalidateQueries, staleTime, gcTime, refetch, hydration.
npx skillsauth add gohypergiant/agent-skills accelint-tanstack-query-best-practicesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Expert patterns for TanStack Query in modern React applications with Next.js App Router and Server Components.
setQueryData(key, (old) => ({ ...old, changed: value })) instead of setQueryData(key, newValue).This skill uses progressive disclosure to minimize context usage. Load references based on your scenario:
MANDATORY - READ ENTIRE FILE: Read query-client-setup.md (~125 lines) and server-integration.md (~151 lines) completely for server/client setup patterns.
Do NOT Load other references for initial setup.
Copy assets/query-client.ts for production-ready configuration.
query-keys.md (~151 lines) for key factory setupserver-integration.mdUse decision tables below for configuration values.
MANDATORY - READ ENTIRE FILE: Read mutations-and-updates.md (~345 lines) completely. Reference patterns-and-pitfalls.md for rollback patterns.
Do NOT Load caching-strategy.md for basic CRUD mutations.
patterns-and-pitfalls.mdfundamentals.md for structural sharingMANDATORY: Read caching-strategy.md (~198 lines) for unified Next.js use cache + TanStack Query + HTTP cache patterns.
Do NOT Load if only using client-side TanStack Query.
| Data Type | staleTime | gcTime | refetchInterval | structuralSharing | Notes | |-----------|-----------|--------|-----------------|-------------------|-------| | Reference/Lookup | 1hr | Infinity | - | true | Countries, categories, static enums | | User Profile | 5min | 10min | - | true | Changes infrequently, moderate freshness | | Real-time Tracking | 5s | 30s | 5s | false | High update frequency, large payloads | | Live Dashboard | 2s | 1min | 2s | Depends on size | Balance freshness vs performance | | Detail View | 30s | 2min | - | true | Fetched on-demand, moderate caching | | Search Results | 1min | 5min | - | true | Cacheable, not time-sensitive |
| Scenario | Pattern | When to Use | |----------|---------|-------------| | Form submission | Pessimistic | Multi-step forms, server validation required, error messages needed before proceeding | | Toggle/checkbox | Optimistic | Binary state changes, low latency required, easy to rollback | | Drag and drop | Optimistic | Immediate visual feedback essential, reordering operations, non-critical data | | Batch operations | Pessimistic | Multiple items, partial failures possible, user needs confirmation of what succeeded | | Life-critical ops | Pessimistic | Medical, financial, safety-critical systems where UI must match server reality | | Audit trail required | Pessimistic | Compliance systems where operator actions must match logged events exactly |
Use hierarchical factories for consistent invalidation:
// Recommended structure
export const keys = {
all: () => ['domain'] as const,
lists: () => [...keys.all(), 'list'] as const,
list: (filters: string) => [...keys.lists(), filters] as const,
details: () => [...keys.all(), 'detail'] as const,
detail: (id: string) => [...keys.details(), id] as const,
};
// Invalidation examples
queryClient.invalidateQueries({ queryKey: keys.all() }); // Invalidate everything
queryClient.invalidateQueries({ queryKey: keys.lists() }); // Invalidate all lists
queryClient.invalidateQueries({ queryKey: keys.detail(id) }); // Invalidate one item
Key stability rules:
| Layer | Purpose | Invalidation Method | Cache Scope | |-------|---------|---------------------|-------------| | Next.js use cache | Reduce database load | revalidateTag() or updateTag() | Cross-request, server-side | | TanStack Query | Client-side state management | queryClient.invalidateQueries() | Per-browser-tab | | Browser HTTP cache | Eliminate network requests | Cache-Control headers | Per-browser |
Unified invalidation strategy:
| Observer Count | Performance Impact | Action Required | |----------------|-------------------|------------------| | 1-5 | Negligible | None | | 6-20 | Minimal | Monitor, no immediate action | | 21-50 | Noticeable on updates | Consider hoisting queries to parent | | 51-100 | Significant overhead | Refactor: hoist queries or use select | | 100+ | Critical impact | Immediate refactor: single query with props distribution |
Diagnosis:
| Pattern | Use Case | Example |
|---------|----------|---------|
| useSuspenseQuery | Server Components integration, Suspense boundaries | useSuspenseQuery({ queryKey, queryFn }) |
| useQuery with enabled | Dependent queries, conditional fetching | useQuery({ queryKey, queryFn, enabled: !!userId }) |
| useQuery with select | Data transformation, subset selection | useQuery({ queryKey, queryFn, select: selectFn }) — extract selectFn to a stable module-level variable; inline functions re-run on every render |
| useMutation optimistic | Low-latency UI updates, easily reversible | useMutation({ onMutate, onError, onSettled }) |
| useMutation pessimistic | High-stakes operations, server validation | useMutation({ onSuccess }) |
| Symptom | Root Cause | Solution | Fallback if Solution Fails |
|---------|------------|----------|---------------------------|
| Data doesn't update after save | Copied query data to useState | Use query data directly, derive with useMemo | Force refetch with refetch() method, check network tab for actual API response |
| Infinite requests | Unstable query keys (Date.now(), unsorted arrays) | Use deterministic key construction | Add staleness detection: const requestCount = useRef(0); useEffect(() => { requestCount.current++; if (requestCount.current > 10) console.error('Infinite loop detected', queryKey); }, [data]); See fundamentals.md for key stability patterns |
| N duplicate requests | Query in every list item | Hoist query to parent, pass data as props | Ensure all components use identical queryKey (same object reference or values): const queryKey = useMemo(() => keys.list(filters), [filters]); Increase staleTime to 30s to deduplicate rapid requests |
| Query fires with undefined params | Missing enabled guard | Add enabled: Boolean(dependency) | Use placeholderData to show loading state, add type guards in queryFn to throw early |
| Slow list rendering | N queries + N observers | Single parent query, distribute via props | Use select to subscribe to subset, implement virtual scrolling to reduce mounted components |
| Cache never clears | gcTime: Infinity on frequently-changing data | Match gcTime to data lifecycle | Force removal with queryClient.removeQueries(), monitor cache size with DevTools |
| UI shows stale data flash | Server cache stale, client cache fresh | Unified invalidation with same keys | Use initialData from server props, set refetchOnMount: false for hydrated queries |
| Optimistic update won't rollback | onError not restoring context | Use context from onMutate in onError | Force invalidation with invalidateQueries, implement manual rollback with previous state snapshot |
| Server hydration mismatch | Timestamp/user-specific data in SSR | Use suppressHydrationWarning on container | Client-only rendering with dynamic import and ssr: false, or normalize timestamps to UTC |
| Query never refetches | enabled: false guard blocking, or gcTime expired | Check enabled conditions, verify query isn't filtered by predicate | Increase gcTime to keep cache alive longer, use refetchInterval for polling behavior, check if staleTime: Infinity is preventing background refetches |
| Server action not invalidating | updateTag/revalidateTag using different keys than queryClient | Use same key factories for both server and client caches | Manually call router.refresh() after server action, verify tag names match query key hierarchy |
| Mutation succeeds but UI doesn't update | Missing onSuccess invalidation or wrong queryKey | Add onSuccess: () => queryClient.invalidateQueries({ queryKey }) | Use setQueryData to manually update cache: queryClient.setQueryData(keys.detail(id), newData), verify queryKey matches exactly |
Step 1: Check observer count in DevTools (use thresholds at lines 136-145)
Step 2: Check data size and update frequency
structuralSharing: false (see fundamentals.md for details)Step 3: Check React DevTools Profiler
Flaky connections:
retry: 3, retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000)Token refresh needed:
Race conditions:
SSR mismatch (hydration error in console):
Client-server data drift:
initialData: serverData, refetchOnMount: falseHydrationBoundary not working:
Calibrate guidance specificity to mutation risk:
| Task Type | Freedom Level | Guidance Format | Example | |-----------|---------------|-----------------|---------| | Query configuration | High freedom | Principles with tables for common patterns | "Match staleTime to business requirements" | | Optimistic updates | Medium freedom | Complete pattern with rollback handling | "Use onMutate/onError/onSettled callbacks" | | QueryClient setup | Low freedom | Exact code with critical security warning | "NEVER use singleton on server - use factory" |
The test: "If the agent makes a mistake, what's the consequence?"
select only runs on successfully cached data — it is never called in error states; put validation and error throwing in queryFntools
Implement QRSPI-planned OpenSpec changes with intelligent parallelization. Use when the user wants to apply a QRSPI change, implement tasks with parallelization, or says "apply this QRSPI change", "implement with parallelization", "run the parallel slices". This skill is specifically designed for changes created via accelint-qrspi that include "Parallelization Strategy" sections in tasks.md. It orchestrates parallel sub-agent execution for independent task slices using OpenSpec CLI workflows. Make sure to use this skill when the user mentions applying QRSPI changes, running parallel implementation, or working on changes with vertical slices.
development
Generate or update an ARCHITECTURE.md living document for any codebase. Use this skill whenever a user mentions "architecture.md", "ARCHITECTURE.md", "document my architecture", "architecture overview", "system architecture", "generate architecture doc", "create architecture file", "update architecture", "architecture diagram", or wants a technical overview of how their project is structured. Make sure to use this skill whenever users want to document how their system works — even if they phrase it as "write up the system", "document the tech stack", "create a technical overview", or "help me describe the architecture". Always prefer this skill over ad-hoc architecture documentation.
development
Automate the QRSPI + OpenSpec planning workflow (Questions → Research → Design → Structure) for spec-driven development. Use this skill when the user wants to plan a ticket, start a QRSPI workflow, create a change with QRSPI, or says "plan this with QRSPI", "use QRSPI to plan", "start QRSPI workflow", "create spec-driven change", or asks about planning a feature/change before implementation. This skill handles ONLY the planning phase — it does NOT implement code. After completion, the user continues with /opsx:apply for implementation.
development
Comprehensive TypeScript/JavaScript coding standards focusing on type safety, defensive programming, and code correctness. Use when (1) Writing or reviewing TS/JS code, (2) Fixing type errors or avoiding any/enum/null, (3) Implementing control flow, state management, or error handling, (4) Applying zero-value pattern or immutability, (5) Code review for TypeScript anti-patterns. Covers naming conventions, function design, return values, bounded iteration, input validation. For performance optimization, use accelint-ts-performance skill. For documentation, use accelint-ts-documentation skill.