React useState Hook删除元素时数组未更新的问题排查
我有一个React表格,其中一列包含操作按钮,可通过removeRow(idx)回调删除对应行。该处理程序能正确筛选移除目标行,但后续尝试删除更多行时,已删除的行会重新出现。通过控制台日志可见,尽管表格显示已移除一行(最多一行),但React并未更新rowData状态,每次console.log('rowData', rowData)都输出原始数组。我曾尝试将初始状态设为空数组,但修改后点击removeRow(idx)函数会直接渲染空表格。请问这是什么原因?
UsersTable.js
import React, { useState, useEffect } from 'react'; import { Link } from 'react-router-dom'; import { MDBDatatable, MDBBtn, MDBIcon } from 'mdbReactUiKit'; import Wrapper from '../Wrapper'; import styled from 'styled-components'; import { rows, cols } from '../../variables/Users'; ... export default function UsersTable() { const [colData, setColData] = useState(cols) const [rowData, setRowData] = useState(addCustomButtons(rows)) function addCustomButtons(rows) { return rows.map((row, idx) => { return { ...row, index: idx, email: ( <StyledLink to='#' onClick={(e) => { window.location.href = `mailto:${row.emailraw}`; e.preventDefault(); }} > {row.emailraw} </StyledLink> ), buttons: ( <> <StyledButton size='sm' floating className='message-btn' onClick={() => console.log(`send a message to ${row.emailraw}`)} > <MDBIcon icon='envelope' /> </StyledButton> <StyledButton outline size='sm' floating className='call-btn' onClick={() => console.log(`edit user settings`)}> <MDBIcon icon='ellipsis-h' /> </StyledButton> <StyledTrash size='sm' floating className='remove-user-btn' onClick={() => removeRow(idx)} > <MDBIcon icon="trash" /> </StyledTrash> </> ), }; }) } // does not work const deleteRow = (index) => { if (rowData.length > 0) { var updatedRows = [...rowData] console.log('1st rowData', updatedRows) var indexToRemove = updatedRows.findIndex(x => x.index === index) console.log('remove', indexToRemove, index) if (indexToRemove > -1) { updatedRows.splice(indexToRemove, 1) var newRows = addCustomButtons(updatedRows) console.log(newRows) setRowData(newRows) // setTableData((prevState) => { // return { // ...prevState, // rows: newRows // } // }) } } } // almost works const removeRow = (index) => { console.log('rowData', rowData) const oldRows = [...rowData] const updatedRows = oldRows.filter((row) => row.index !== index); console.log('updated', updatedRows) setRowData(updatedRows) setTimeout(() => { console.log('timer', rowData) }, 3000) } const Table = ({ colData, rowData }) => { let tableData = { columns: colData, rows: rowData } useEffect(() => { tableData = { columns: colData, rows: rowData } }, [rowData]) return ( <StyledTable hover striped data={tableData} entriesOptions={[5, 10, 20]} entries={10} fixedHeader maxHeight='460px' /> ) } return ( <Wrapper> <TableContainer> <Table colData={colData} rowData={rowData} /> </TableContainer> </Wrapper> ); }
问题根源与解决方案
1. 闭包导致旧索引绑定
初始调用addCustomButtons(rows)生成行数据时,删除按钮的removeRow(idx)绑定的是原始数组的循环索引。删除一行后,rowData更新,但新行没有重新生成按钮并绑定当前正确的索引,后续删除时,row.index !== index中的index仍是旧索引,无法匹配新行的索引值,导致筛选失效、已删行“重现”。
2. 状态更新未用函数式更新
removeRow中直接依赖当前rowData生成新数组,由于React状态更新是异步的,连续操作时可能获取到旧的rowData值,导致状态更新错误,日志始终输出原始数组。
3. 子组件Table的无效useEffect逻辑
子组件中先声明let tableData再通过useEffect赋值是错误的——组件渲染时会重新初始化tableData,useEffect的赋值不会影响当前渲染,导致表格无法响应rowData的变化。
修正后的关键代码
修正removeRow函数(函数式更新+重新生成按钮)
const removeRow = (index) => { // 函数式更新确保基于最新状态计算新值 setRowData(prevRowData => { const filteredRows = prevRowData.filter(row => row.index !== index); // 重新生成按钮并更新行索引 return addCustomButtons(filteredRows); }); }
修正子组件Table
const Table = ({ colData, rowData }) => { // 直接基于props生成tableData,无需useEffect const tableData = { columns: colData, rows: rowData }; return ( <StyledTable hover striped data={tableData} entriesOptions={[5, 10, 20]} entries={10} fixedHeader maxHeight='460px' /> ); }
初始状态设为空数组的问题说明
如果初始rows为空数组,调用addCustomButtons(rows)会生成空的rowData,因此表格直接渲染为空。若需支持初始空状态,可在addCustomButtons中添加空数组判断,或者初始状态设为[],后续通过接口请求后再调用addCustomButtons更新rowData。
内容的提问来源于stack exchange,提问作者melv3223

