tanstack-query
TanStack Query for data fetching. Covers queries, mutations, and caching. Use for server state management in React. USE WHEN: user mentions "tanstack query", "react query", "data fetching", "API calls", asks about "cache management", "mutations", "infinite scroll", "optimistic updates", "prefetching", "server state", "useQuery", "useMutation" DO NOT USE FOR: client state - use `zustand` or `redux-toolkit`; Vue apps - use `pinia` with composables; static data - use React context
What this skill does
# TanStack Query Core Knowledge
> **Deep Knowledge**: Use `mcp__documentation__fetch_docs` with technology: `tanstack-query` for comprehensive documentation.
## Setup
```tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60, // 1 minute
retry: 1,
},
},
});
function App() {
return (
<QueryClientProvider client={queryClient}>
<MyApp />
</QueryClientProvider>
);
}
```
## Queries
```tsx
import { useQuery } from '@tanstack/react-query';
function UserProfile({ userId }: { userId: string }) {
const { data, isLoading, error } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
});
if (isLoading) return <Spinner />;
if (error) return <Error message={error.message} />;
return <div>{data.name}</div>;
}
// With options
const { data } = useQuery({
queryKey: ['users', { status: 'active' }],
queryFn: () => fetchUsers({ status: 'active' }),
staleTime: 5 * 60 * 1000,
refetchOnWindowFocus: false,
enabled: !!userId, // Conditional fetch
});
```
## Mutations
```tsx
import { useMutation, useQueryClient } from '@tanstack/react-query';
function CreateUser() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (newUser: CreateUserInput) => createUser(newUser),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
return (
<form onSubmit={(e) => {
e.preventDefault();
mutation.mutate({ name: 'John' });
}}>
<button disabled={mutation.isPending}>
{mutation.isPending ? 'Creating...' : 'Create'}
</button>
</form>
);
}
```
## Optimistic Updates
```tsx
const mutation = useMutation({
mutationFn: updateUser,
onMutate: async (newData) => {
await queryClient.cancelQueries({ queryKey: ['user', id] });
const previous = queryClient.getQueryData(['user', id]);
queryClient.setQueryData(['user', id], newData);
return { previous };
},
onError: (err, newData, context) => {
queryClient.setQueryData(['user', id], context.previous);
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['user', id] });
},
});
```
## When NOT to Use This Skill
| Scenario | Use Instead |
|----------|-------------|
| Client-side UI state (modals, form inputs) | `zustand` or React state |
| Vue 3 applications | `pinia` with custom composables |
| Static configuration data | React Context API |
| Real-time WebSocket data | Custom hooks with WebSocket + Zustand |
| GraphQL queries | `@apollo/client` or `urql` |
## Anti-Patterns
| Anti-Pattern | Why It's Bad | Correct Approach |
|--------------|--------------|------------------|
| Using queries for client state | Unnecessary complexity, wrong abstraction | Use Zustand or React state |
| Not using query keys properly | Cache collisions, wrong data | Use query key factory pattern |
| Fetching on every render | Performance issues | Set proper `staleTime` and `gcTime` |
| Manual cache invalidation everywhere | Hard to maintain | Use mutation's `onSuccess` with `invalidateQueries` |
| Ignoring loading/error states | Poor UX | Always handle `isLoading` and `error` |
| Using `refetch()` instead of `invalidate()` | Bypasses cache, wastes requests | Use `invalidateQueries()` for revalidation |
| Not prefetching predictable navigation | Slow perceived performance | Prefetch on hover/mount |
| Storing queries in global state | Defeats TanStack Query purpose | Let TanStack Query manage cache |
| No retry strategy for transient errors | Failed requests on network blips | Configure retry with backoff |
| Missing query key dependencies | Stale data when params change | Include all variables in query key |
## Quick Troubleshooting
| Issue | Cause | Solution |
|-------|-------|----------|
| Queries not refetching | `staleTime` too high or `enabled: false` | Lower `staleTime` or check `enabled` condition |
| "No QueryClient set" error | Missing `QueryClientProvider` | Wrap app with `<QueryClientProvider client={queryClient}>` |
| Mutations not updating UI | Not invalidating queries | Call `queryClient.invalidateQueries()` in `onSuccess` |
| Infinite refetch loop | Query key changes on every render | Stabilize query key with `useMemo` or constants |
| SSR hydration mismatch | Server/client data out of sync | Use `HydrationBoundary` with dehydrated state |
| Memory leaks | Queries never garbage collected | Set proper `gcTime` (default 5 minutes) |
| Optimistic update reverted incorrectly | Context not returned from `onMutate` | Return previous value from `onMutate`, restore in `onError` |
| Multiple identical requests | No deduplication interval | Set `dedupingInterval` in query options |
## Production Readiness
### Query Client Configuration
```tsx
// lib/queryClient.ts
import { QueryClient, QueryCache, MutationCache } from '@tanstack/react-query';
export const queryClient = new QueryClient({
queryCache: new QueryCache({
onError: (error, query) => {
// Global error handling for queries
if (query.state.data !== undefined) {
// Only show error toasts for background refetch failures
toast.error(`Background update failed: ${error.message}`);
}
// Log to monitoring
logError({ type: 'query', key: query.queryKey, error });
},
}),
mutationCache: new MutationCache({
onError: (error, _variables, _context, mutation) => {
logError({ type: 'mutation', key: mutation.options.mutationKey, error });
},
}),
defaultOptions: {
queries: {
staleTime: 1000 * 60, // 1 minute
gcTime: 1000 * 60 * 5, // 5 minutes (formerly cacheTime)
retry: (failureCount, error) => {
// Don't retry on 4xx errors
if (error.status >= 400 && error.status < 500) return false;
return failureCount < 3;
},
refetchOnWindowFocus: process.env.NODE_ENV === 'production',
},
mutations: {
retry: false,
},
},
});
```
### Query Key Factory
```typescript
// lib/queryKeys.ts - Organized query keys
export const queryKeys = {
users: {
all: ['users'] as const,
lists: () => [...queryKeys.users.all, 'list'] as const,
list: (filters: UserFilters) => [...queryKeys.users.lists(), filters] as const,
details: () => [...queryKeys.users.all, 'detail'] as const,
detail: (id: string) => [...queryKeys.users.details(), id] as const,
},
posts: {
all: ['posts'] as const,
byUser: (userId: string) => [...queryKeys.posts.all, 'user', userId] as const,
},
} as const;
// Usage
const { data } = useQuery({
queryKey: queryKeys.users.detail(userId),
queryFn: () => fetchUser(userId),
});
// Invalidate all user queries
queryClient.invalidateQueries({ queryKey: queryKeys.users.all });
```
### Error Handling
```tsx
// hooks/useApiQuery.ts
import { UseQueryOptions, useQuery } from '@tanstack/react-query';
interface ApiError {
status: number;
message: string;
}
export function useApiQuery<TData>(
options: UseQueryOptions<TData, ApiError>
) {
return useQuery({
...options,
throwOnError: (error) => {
// Only throw for critical errors that should trigger error boundary
return error.status >= 500;
},
});
}
// With error boundary
function UserProfile({ userId }: { userId: string }) {
const { data, error, isLoading } = useApiQuery({
queryKey: queryKeys.users.detail(userId),
queryFn: () => fetchUser(userId),
});
if (isLoading) return <Skeleton />;
if (error) {
if (error.status === 404) return <NotFound />;
return <ErrorMessage message={error.message} />;
}
return <UserCard user={data} />;
}
```
### Prefetching & SSR
```tsx
// Next.js App Router example
// app/users/page.tsx
import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query';
export default async function UsersPage() {
const queryCliRelated in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.