React中ShanCN Table删除行时rowSelection状态累加问题求助
TanStack Table(ShanCN Table)删除行后选中状态累加问题排查与解决
问题场景
使用基于TanStack Table的ShanCN Table实现部门行删除功能,通过selectedRows钩子获取选中行索引,将setSelectedRows作为props传给表格组件。前两三次删除操作正常,但后续删除时,rowSelection状态会保留之前的选中值并累加——例如删除行索引['1']后,再选中行2删除时,rowSelection的值变为['1','2'],而非预期的['2']。尝试用useCallback优化但无效,怀疑是闭包和异步特性导致。
相关代码
const DepartmentsAndRolesPage = () => { const [departments, setDepartments] = useState<departmentTypes[]>([]); const [selectedRows, setSelectedRows] = useState({}); const [rowIndexes, setRowIndexes] = useState<string[]>([]); useEffect(() => { getDepartment(); }, []); useEffect(() => { const indexesToAdd = Object.keys(selectedRows); setRowIndexes(indexesToAdd); }, [selectedRows]); const deleteDepartment = useCallback(async () => { if (rowIndexes.length === 0) { return; } if (rowIndexes.length === 1) { const filteredDepartment = departments[parseInt(rowIndexes[0])]; const id = filteredDepartment._id; const response = await axios.delete(`/api/department/delete/${id}`); if (response.statusText === "OK") { getDepartment(); setSelectedRows({}); } return; } if (rowIndexes.length > 1) { const departmentsToDelete = rowIndexes.map( (rowIndex) => departments[parseInt(rowIndex)]._id ); const response = await axios.post("/api/department/delete", { _id: departmentsToDelete, }); if (response.statusText === "OK") { getDepartment(); setSelectedRows({}); } return; } }, [departments, rowIndexes, toast]); return (/* UI代码 */) };
问题原因
- 行索引的不稳定性:删除行后,表格剩余行的索引会发生偏移,而TanStack Table的
rowSelection默认绑定行在原始数据中的索引。当旧索引被删除后,新行索引和之前的选中状态产生映射混乱,导致选中状态无法正确重置。 - 闭包与异步状态更新:
deleteDepartment中依赖的departments和rowIndexes是闭包捕获的旧值,异步请求完成后调用setSelectedRows({})时,可能因状态更新的异步性,导致表格组件未及时同步最新选中状态,进而出现累加问题。
解决方案
方案1:改用行数据的唯一ID作为选中标识(已验证有效)
放弃使用行索引作为选中依据,改用行数据的_id(唯一标识)绑定选中状态:
- 在表格配置中,设置
rowSelection的idAccessor为row => row._id,让选中状态绑定行的唯一ID而非索引。 - 删除操作时,直接从
selectedRows中提取选中的_id,无需依赖行索引,彻底避免索引偏移带来的问题。
方案2:优化现有代码逻辑(若坚持使用行索引)
- 用函数式更新清空选中状态:确保在
getDepartment完成后,拿到最新状态进行清空:setSelectedRows(prev => ({})); - 减少中间状态依赖:直接在
deleteDepartment中从selectedRows提取索引,省去rowIndexes中间状态,降低闭包捕获旧值的概率:const deleteDepartment = useCallback(async () => { const selectedIndexes = Object.keys(selectedRows); if (selectedIndexes.length === 0) return; // 后续逻辑改用selectedIndexes替代rowIndexes }, [departments, selectedRows, toast]); - 强制同步数据与选中状态:在
getDepartment获取最新数据后,立即清空选中状态,因为旧索引在新数据中已失效。
内容的提问来源于stack exchange,提问作者yanger rai
相关产品推荐
相关产品推荐

