| 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
- URL 状态:用 nuqs 的
useQueryStates 管理 page 与所有筛选参数;翻页/筛选只调 nuqs setter,不维护 currentPage/filters 的 useState。
- 数据加载:queryKey 包含从 URL 解析出的全部参数(如
[page, ...filters]);数据请求只依赖这些值,一次 URL 变化对应一次请求。
- 分层:Container 负责 nuqs + Query + 分发状态;Filter 只接收当前值与 onChange;Display 只接收 data/分页信息。
- 筛选变更:任意筛选变化时用
setUrlState({ page: 1, ...newFilters }) 一次性更新并重置分页。
- 加载状态:首次用
isLoading(Skeleton),后续用 isFetching;配置 placeholderData: keepPreviousData 防闪烁。
反模式:双源导致翻页重复请求
现象: 翻页时同一 ?page=N 请求 2~3 次。
原因: 页码/筛选同时被本地 state 和 URL 驱动(如 setCurrentPage + router.push,再在 useEffect 里从 searchParams 解析并 setState),多次 setState 触发多次依赖它们的 effect。
正确: 以 URL 为唯一数据源,用 nuqs 管理 page 与筛选;数据加载只依赖 nuqs 解析出的值。
Checklist
实现或审查可筛选/分页页时:
Common Mistakes
| 错误做法 | 正确做法 |
|---|
| 筛选变更时忘记重置分页 | 始终 setUrlState({ page: 1, ...changes }) |
用 isLoading 控制所有加载状态 | 首次 isLoading,后续 isFetching |
Filter 内部调用 setUrlState | Filter 只接收回调,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。