React Query服务端分页场景下如何实现全页行选择
TanStack Query服务端分页下实现跨页行选择解决方案
问题背景
使用Tanstack Query实现服务端分页的用户表格,已访问页面数据会被缓存,但实现行选择时,点击"全选所有页面"仅能选中当前页数据,无法识别缓存的其他页面数据。
用户提供的核心代码片段:
组件分页与查询逻辑
const [{ pageIndex, pageSize }, setPagination] = React.useState<PaginationState>({ pageIndex: 1, pageSize: 10, }); const pagination = React.useMemo( () => ({ pageIndex, pageSize, }), [pageIndex, pageSize] ); const query = useUsers(pagination); const { data: users, isLoading } = query;
TanStack Query查询封装
import { useQuery } from '@tanstack/react-query'; import { PaginationState } from '@tanstack/react-table'; import React from 'react'; import config from '../../../config'; import { PaginatedResult } from '../../../core/models/pagination/PaginatedResult'; import { User } from '../../../core/models/users/User'; import usersKeys from '../../../core/api/queries/users'; import serverSideDataUrlBuilder from '../../../core/helpers/serverSideDataUrlBuilder'; async function fetchUsers(pagination: PaginationState) { const urlBuilder = serverSideDataUrlBuilder(`${config.API.url}/users`); urlBuilder.clear(); urlBuilder.addPagination(pagination.pageIndex, pagination.pageSize); const url = urlBuilder.build(); const response = await fetch(url, { method: 'GET', }); if (!response.ok) { throw new Error('Failed to fetch users'); } const jsonResponse = await response.json(); return jsonResponse as PaginatedResult<Array<User>>; } const useUsers = (pagination: PaginationState) => useQuery<PaginatedResult<Array<User>>, Error>( [usersKeys.paginated(pagination)], () => fetchUsers(pagination), { select: React.useCallback( (data: PaginatedResult<Array<User>>) => data, [] ), } ); export default useUsers;
选择事件处理
const handleSelectionChange = (value: string) => { const selectTableValue = getSelectTableValue(value); if ( selectTableValue.label === 'Select all' && selectTableValue.allPages ) { table.toggleAllRowsSelected(true); } else if ( selectTableValue.label === 'Deselect all' && selectTableValue.allPages ) { table.toggleAllRowsSelected(false); } else if ( selectTableValue.label === 'Select all' && !selectTableValue.allPages ) { table.toggleAllPageRowsSelected(true); } else if ( selectTableValue.label === 'Deselect all' && !selectTableValue.allPages ) { table.toggleAllPageRowsSelected(false); } };
解决方案
TanStack Table默认全选逻辑仅针对当前渲染行,要实现跨页全选,需手动维护全局选中状态并结合TanStack Query的缓存数据。
1. 新增全局选中状态管理
在组件中添加状态,区分"全选所有页面"标记和手动选中的行ID:
import { useQueryClient } from '@tanstack/react-query'; import { RowSelectionState } from '@tanstack/react-table'; // ... 其他导入 const UserTable = () => { const queryClient = useQueryClient(); // ... 原有分页与查询逻辑 // 新增全局选中状态 const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({}); const [selectAllPages, setSelectAllPages] = React.useState(false); // ... 其他代码 };
2. 获取所有缓存的分页数据
通过queryClient获取已缓存的所有分页用户数据,用于跨页选中逻辑:
// 获取所有缓存的用户数据 const getCachedUsers = () => { // 匹配所有分页查询的缓存(需确保usersKeys.base是分页查询的基础key) const allCachedQueries = queryClient.getQueriesData({ queryKey: [usersKeys.base] }); return allCachedQueries.flatMap(([_, data]) => { if (data?.items) { return data.items; } return []; }); };
3. 自定义表格选中逻辑
修改表格实例配置,替换默认选中判断逻辑,优先使用全局全选标记:
const table = useReactTable({ data: users?.items || [], columns: yourColumns, // 替换为你的列定义 getCoreRowModel: getCoreRowModel(), // 自定义选中状态判断 getIsRowSelected: (row) => { if (selectAllPages) return true; return !!rowSelection[row.id]; }, // 处理行选择变更 onRowSelectionChange: (newSelection) => { if (!selectAllPages) { setRowSelection(newSelection); } }, state: { pagination: { pageIndex, pageSize }, rowSelection, }, onPaginationChange: (newPagination) => { setPagination(newPagination); }, });
4. 更新全选事件处理
修改handleSelectionChange,适配全局全选逻辑:
const handleSelectionChange = (value: string) => { const selectTableValue = getSelectTableValue(value); if (selectTableValue.label === 'Select all' && selectTableValue.allPages) { setSelectAllPages(true); // 可选:同步更新选中ID集合(用于后续提交) // const cachedUsers = getCachedUsers(); // const allSelected = cachedUsers.reduce((acc, user) => { // acc[user.id] = true; // return acc; // }, {} as RowSelectionState); // setRowSelection(allSelected); } else if (selectTableValue.label === 'Deselect all' && selectTableValue.allPages) { setSelectAllPages(false); setRowSelection({}); } else if (selectTableValue.label === 'Select all' && !selectTableValue.allPages) { setSelectAllPages(false); table.toggleAllPageRowsSelected(true); } else if (selectTableValue.label === 'Deselect all' && !selectTableValue.allPages) { setSelectAllPages(false); table.toggleAllPageRowsSelected(false); } };
5. 处理未缓存页面的全选(可选)
若需支持选中所有页面(包括未缓存数据),建议:
- 后端提供获取所有用户ID的轻量接口,避免全量拉取数据
- 提交时传递
selectAll: true标记,由后端处理全量数据
关键注意事项
- 确保
usersKeys.base是分页查询的基础Key,保证getQueriesData能匹配所有分页缓存 - 行ID必须唯一,建议使用用户ID作为表格行的
id字段 - 数据量极大时,避免全量拉取缓存数据,优先使用后端标记全选的方案
内容的提问来源于stack exchange,提问作者bialob
相关产品推荐
相关产品推荐

