You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 22:12:08