AgentSkillsCN

filterable-data-page

实现可筛选、分页的列表页面,以URL作为单一可信来源(NUQS),并结合TanStack Query,避免重复请求,同时支持可共享的链接。在构建带有筛选器、分页、URL同步状态的列表页面时,或在解决页面切换时多次请求的问题时使用此功能。

SKILL.md
--- frontmatter
name: filterable-data-page
description: Implements filterable, paginated list pages with URL as single source of truth (nuqs) and TanStack Query to avoid duplicate requests and support shareable links. Use when building list pages with filters, pagination, URL-synced state, or when fixing multiple requests on page change.

Filterable Data Page

Overview

可筛选、分页列表页的标准实现:URL 为唯一数据源(nuqs),数据加载用 TanStack Query,Container/Filter/Display 三层分离。避免「state + URL 双源」导致的翻页重复请求。

When to Use

适用: 多筛选条件、分页、分享链接/刷新保持、切换筛选需保留旧数据防闪烁。
不适用: 无筛选、筛选不需进 URL、非 React。

Quick Start

  1. URL 状态:用 nuqs 的 useQueryStates 管理 page 与所有筛选参数;翻页/筛选只调 nuqs setter,不维护 currentPage/filters 的 useState。
  2. 数据加载:queryKey 包含从 URL 解析出的全部参数(如 [page, ...filters]);数据请求只依赖这些值,一次 URL 变化对应一次请求。
  3. 分层:Container 负责 nuqs + Query + 分发状态;Filter 只接收当前值与 onChange;Display 只接收 data/分页信息。
  4. 筛选变更:任意筛选变化时用 setUrlState({ page: 1, ...newFilters }) 一次性更新并重置分页。
  5. 加载状态:首次用 isLoading(Skeleton),后续用 isFetching;配置 placeholderData: keepPreviousData 防闪烁。

反模式:双源导致翻页重复请求

现象: 翻页时同一 ?page=N 请求 2~3 次。
原因: 页码/筛选同时被本地 state 和 URL 驱动(如 setCurrentPage + router.push,再在 useEffect 里从 searchParams 解析并 setState),多次 setState 触发多次依赖它们的 effect。
正确: 以 URL 为唯一数据源,用 nuqs 管理 page 与筛选;数据加载只依赖 nuqs 解析出的值。

Checklist

实现或审查可筛选/分页页时:

  • 使用 nuqs useQueryStates 管理 page 与筛选,无单独 useState 存 page/filters
  • 翻页/筛选只更新 URL(nuqs setter),不先 setCurrentPage 再 router.push
  • queryKey / 数据请求依赖仅来自 URL 解析结果
  • 筛选变更时重置分页:setUrlState({ page: 1, ...changes })
  • 配置 placeholderData: keepPreviousData;区分 isLoading / isFetching
  • Filter 只接收回调;Display 只接收 props;Container 负责 URL 与数据

Common Mistakes

错误做法正确做法
筛选变更时忘记重置分页始终 setUrlState({ page: 1, ...changes })
isLoading 控制所有加载状态首次 isLoading,后续 isFetching
Filter 内部调用 setUrlStateFilter 只接收回调,Container 负责 URL
每个筛选条件单独 useState统一 useQueryStates 管理 URL 参数
忘记 placeholderData配置 keepPreviousData 防闪烁
搜索框用 history: 'push'搜索框用 history: 'replace'
级联筛选分两次 setState一次性:{ leader: x, event: null, page: 1 }
Display 内部获取数据Display 只接 props,Container 负责数据
useState 存 page/filters + 翻页时 setCurrentPage + router.push + useEffect 从 searchParams 解析 setState双源导致多次请求;改用 nuqs 或翻页只 updateUrl 不 setCurrentPage

详细说明

Pattern 1(nuqs 解析器、useQueryStates、重置分页、级联筛选)、Pattern 2(queryKey、keepPreviousData、isLoading/isFetching、渲染优先级)、Pattern 3(三层架构与数据流)见 reference.md