重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
accelint-tanstack-query-best-practices by gohypergiant/agent-skills
npx skills add https://github.com/gohypergiant/agent-skills --skill accelint-tanstack-query-best-practices在现代 React 应用中使用 Next.js App Router 和 Server Components 的 TanStack Query 专家模式。
structuralSharing: false 来禁用。setQueryData(key, (old) => ({ ...old, changed: value })) 而不是 。广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
setQueryData(key, newValue)此技能使用渐进式披露来最小化上下文使用。根据你的场景加载参考:
强制 - 阅读整个文件:完整阅读 query-client-setup.md(约 125 行)和 server-integration.md(约 151 行),了解服务器/客户端设置模式。不要加载其他参考以进行初始设置。
复制 assets/query-client.ts 以获取生产就绪的配置。
query-keys.md(约 151 行)以了解键工厂设置server-integration.md使用下面的决策表来配置值。
强制 - 阅读整个文件:完整阅读 mutations-and-updates.md(约 345 行)。参考 patterns-and-pitfalls.md 以了解回滚模式。不要加载 caching-strategy.md 以进行基本的 CRUD 变更。
patterns-and-pitfalls.mdfundamentals.md 以了解结构共享强制:阅读 caching-strategy.md(约 198 行)以了解统一的 Next.js use cache + TanStack Query + HTTP 缓存模式。不要加载 如果只使用客户端 TanStack Query。
| 数据类型 | staleTime | gcTime | refetchInterval | structuralSharing | 备注 |
|---|---|---|---|---|---|
| 参考/查找 | 1小时 | Infinity | - | true | 国家、类别、静态枚举 |
| 用户资料 | 5分钟 | 10分钟 | - | true | 不常更改,中等新鲜度 |
| 实时跟踪 | 5秒 | 30秒 | 5秒 | false | 高更新频率,大负载 |
| 实时仪表板 | 2秒 | 1分钟 | 2秒 | 取决于大小 | 平衡新鲜度与性能 |
| 详情视图 | 30秒 | 2分钟 | - | true | 按需获取,中等缓存 |
| 搜索结果 | 1分钟 | 5分钟 | - | true | 可缓存,对时间不敏感 |
| 场景 | 模式 | 何时使用 |
|---|---|---|
| 表单提交 | 悲观 | 多步骤表单、需要服务器验证、在继续前需要错误消息 |
| 切换/复选框 | 乐观 | 二进制状态更改、需要低延迟、易于回滚 |
| 拖放 | 乐观 | 即时视觉反馈至关重要、重新排序操作、非关键数据 |
| 批量操作 | 悲观 | 多个项目、可能出现部分失败、用户需要确认成功内容 |
| 生命关键操作 | 悲观 | 医疗、金融、安全关键系统,其中 UI 必须与服务器现实匹配 |
| 需要审计追踪 | 悲观 | 合规系统,其中操作员操作必须与记录的事件完全匹配 |
使用分层工厂实现一致的失效:
// 推荐结构
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,
};
// 失效示例
queryClient.invalidateQueries({ queryKey: keys.all() }); // 使所有内容失效
queryClient.invalidateQueries({ queryKey: keys.lists() }); // 使所有列表失效
queryClient.invalidateQueries({ queryKey: keys.detail(id) }); // 使单个项目失效
键稳定性规则:
| 层级 | 目的 | 失效方法 | 缓存范围 |
|---|---|---|---|
| Next.js use cache | 减少数据库负载 | revalidateTag() 或 updateTag() | 跨请求,服务器端 |
| TanStack Query | 客户端状态管理 | queryClient.invalidateQueries() | 每个浏览器标签页 |
| 浏览器 HTTP 缓存 | 消除网络请求 | Cache-Control 头 | 每个浏览器 |
统一失效策略:
| 观察者计数 | 性能影响 | 所需操作 |
|---|---|---|
| 1-5 | 可忽略 | 无 |
| 6-20 | 最小 | 监控,无需立即操作 |
| 21-50 | 更新时明显 | 考虑将查询提升到父组件 |
| 51-100 | 显著开销 | 重构:提升查询或使用 select |
| 100+ | 关键影响 | 立即重构:使用 props 分发的单一查询 |
诊断:
| 模式 | 使用场景 | 示例 |
|---|---|---|
| useSuspenseQuery | 服务器组件集成、Suspense 边界 | useSuspenseQuery({ queryKey, queryFn }) |
| useQuery with enabled | 依赖查询、条件获取 | useQuery({ queryKey, queryFn, enabled: !!userId }) |
| useQuery with select | 数据转换、子集选择 | useQuery({ queryKey, queryFn, select: selectFn }) — 将 selectFn 提取到稳定的模块级变量;内联函数会在每次渲染时重新运行 |
| useMutation optimistic | 低延迟 UI 更新、易于反转 | useMutation({ onMutate, onError, onSettled }) |
| useMutation pessimistic | 高风险操作、服务器验证 | useMutation({ onSuccess }) |
| 症状 | 根本原因 | 解决方案 | 如果解决方案失败的回退方案 |
|---|---|---|---|
| 保存后数据不更新 | 将查询数据复制到 useState | 直接使用查询数据,通过 useMemo 派生 | 使用 refetch() 方法强制重新获取,检查网络标签以获取实际的 API 响应 |
| 无限请求 | 不稳定的查询键(Date.now()、未排序的数组) | 使用确定性键构造 | 添加陈旧性检测:const requestCount = useRef(0); useEffect(() => { requestCount.current++; if (requestCount.current > 10) console.error('Infinite loop detected', queryKey); }, [data]); 参见 fundamentals.md 了解键稳定性模式 |
| N 个重复请求 | 每个列表项都有查询 | 将查询提升到父组件,将数据作为 props 传递 | 确保所有组件使用相同的 queryKey(相同的对象引用或值):const queryKey = useMemo(() => keys.list(filters), [filters]); 将 staleTime 增加到 30 秒以去重快速请求 |
| 查询使用未定义参数触发 | 缺少启用守卫 | 添加 enabled: Boolean(dependency) | 使用 placeholderData 显示加载状态,在 queryFn 中添加类型守卫以提前抛出错误 |
| 列表渲染缓慢 | N 个查询 + N 个观察者 | 单一父查询,通过 props 分发 | 使用 select 订阅子集,实现虚拟滚动以减少挂载的组件 |
| 缓存从不清理 | 在频繁更改的数据上使用 gcTime: Infinity | 将 gcTime 与数据生命周期匹配 | 使用 queryClient.removeQueries() 强制移除,使用 DevTools 监控缓存大小 |
| UI 显示陈旧数据闪烁 | 服务器缓存陈旧,客户端缓存新鲜 | 使用相同键的统一失效 | 使用服务器 props 的 initialData,为已水合的查询设置 refetchOnMount: false |
| 乐观更新不会回滚 | onError 未恢复上下文 | 在 onError 中使用来自 onMutate 的上下文 | 使用 invalidateQueries 强制失效,使用先前状态快照实现手动回滚 |
| 服务器水合不匹配 | SSR 中的时间戳/用户特定数据 | 在容器上使用 suppressHydrationWarning | 使用动态导入和 ssr: false 进行仅客户端渲染,或将时间戳规范化为 UTC |
| 查询从不重新获取 | enabled: false 守卫阻塞,或 gcTime 已过期 | 检查启用条件,验证查询未被谓词过滤 | 增加 gcTime 以保持缓存更长时间,使用 refetchInterval 进行轮询行为,检查 staleTime: Infinity 是否阻止了后台重新获取 |
| 服务器操作未失效 | updateTag/revalidateTag 使用的键与 queryClient 不同 | 对服务器和客户端缓存使用相同的键工厂 | 在服务器操作后手动调用 router.refresh(),验证标签名称是否与查询键层次结构匹配 |
| 变更成功但 UI 未更新 | 缺少 onSuccess 失效或错误的 queryKey | 添加 onSuccess: () => queryClient.invalidateQueries({ queryKey }) | 使用 setQueryData 手动更新缓存:queryClient.setQueryData(keys.detail(id), newData),验证 queryKey 是否完全匹配 |
步骤 1:在 DevTools 中检查观察者计数(使用第 136-145 行的阈值)
步骤 2:检查数据大小和更新频率
structuralSharing: false(参见 fundamentals.md 了解详情)步骤 3:检查 React DevTools Profiler
不稳定连接:
retry: 3, retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000)需要令牌刷新:
竞态条件:
SSR 不匹配(控制台中的水合错误):
客户端-服务器数据漂移:
initialData: serverData, refetchOnMount: falseHydrationBoundary 不工作:
根据变更风险校准指导的特定性:
| 任务类型 | 自由度级别 | 指导格式 | 示例 |
|---|---|---|---|
| 查询配置 | 高自由度 | 带有常见模式表格的原则 | "将 staleTime 与业务需求匹配" |
| 乐观更新 | 中等自由度 | 带有回滚处理的完整模式 | "使用 onMutate/onError/onSettled 回调" |
| QueryClient 设置 | 低自由度 | 带有关键安全警告的确切代码 | "绝对不要在服务器上使用单例 - 使用工厂" |
测试: "如果代理犯了错误,后果是什么?"
select 仅对成功缓存的数据运行 — 它永远不会在错误状态下被调用;将验证和错误抛出放在 queryFn 中每周安装次数
84
仓库
GitHub 星标数
7
首次出现
2026 年 2 月 7 日
安全审计
安装于
codex84
claude-code73
opencode70
gemini-cli69
cursor69
amp68
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 |
| 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 |
| 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 |
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 queryFnWeekly Installs
84
Repository
GitHub Stars
7
First Seen
Feb 7, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex84
claude-code73
opencode70
gemini-cli69
cursor69
amp68
GSAP React 动画库使用指南:useGSAP Hook 与最佳实践
3,500 周安装
| Detail View | 30s | 2min | - | true | Fetched on-demand, moderate caching |
| Search Results | 1min | 5min | - | true | Cacheable, not time-sensitive |
| Low-latency UI updates, easily reversible |
useMutation({ onMutate, onError, onSettled }) |
| useMutation pessimistic | High-stakes operations, server validation | useMutation({ onSuccess }) |
| 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 |