React.js卡片列表删除指定元素时始终删除末尾新增卡片问题排查
问题解决
根因分析
循环渲染所有卡片时,所有卡片共用了全局同一组open、anchorEl状态控制菜单显隐。点击任意卡片的更多按钮时,全局状态更新后所有卡片的菜单都会匹配最新状态,最终实际响应删除操作的是循环渲染的最后一张卡片,所以永远会传入最后一张卡片的_id触发删除。
修复方案
方案1:抽离独立卡片子组件(推荐)
把单张卡片的逻辑单独抽为子组件,每个子组件内部自己维护菜单的open、anchorEl状态,各个卡片之间状态完全隔离,不会出现互相干扰的问题:
// 抽离的单张卡片子组件 const ApplicationCard = ({ application, onDelete, classes }) => { const [anchorEl, setAnchorEl] = useState(null); const open = Boolean(anchorEl); const handleClick = (e) => setAnchorEl(e.currentTarget); const handleClose = () => setAnchorEl(null); const formattedDate = moment(application.dateApplied).format('MM-DD-YY'); return ( <Card variant="outlined" style={{height: '250px', width: '300px'}}> <CardHeader action={ <IconButton color="primary" onClick={handleClick}> <MoreVertIcon fontSize="large" /> </IconButton> } avatar={ <Avatar src={application.companyLogo} aria-label={`company-logo`} style={{width: '55px', height: '55px'}} /> } title={application.company} subheader={`Applied on: ${formattedDate}`} classes={{ title: classes.headerTitle, }} /> <Menu elevation={1} id={`menu-${application._id}`} anchorEl={anchorEl} open={open} onClose={handleClose} style={{shadows: 'none'}} MenuListProps={{ 'aria-labelledby': 'basic-button', }}> <MenuItem onClick={() => { onDelete(application._id) handleClose() }}> Delete Application </MenuItem> </Menu> <CardContent style={{paddingTop: 0, paddingLeft: '25px', paddingRight: '25px'}}> <Typography variant="h5" component="div"> {application.position} </Typography> <Typography style={{mb: 1.5}} color="text.secondary"> {application.status} </Typography> <Typography variant="body2">{application.comments}</Typography> </CardContent> </Card> ) } // 原父组件简化后逻辑 const displayApplications = (props) => { const {applications, setError, classes} = props; const deleteApplicationHandler = async (id) => { const config = { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${localStorage.getItem('authToken')}`, }, }; try { // 补充await等待删除请求完成 await axios.delete(`api/applications/deleteApplication/${id}`, config); // 不需要刷新页面,可直接更新前端列表更顺滑 window.location.reload(); } catch (error) { setError(error.response.data.error); } }; if (applications.length > 0) { return applications.map((application) => ( <ApplicationCard key={application._id} application={application} onDelete={deleteApplicationHandler} classes={classes} /> )); } else { return <h3>No applications yet </h3>; } };
方案2:调整全局状态存储格式
如果不想抽离子组件,可以把anchorEl存储为当前激活的卡片id,循环时判断当前卡片是否匹配激活id来控制菜单显隐即可。
其他优化点
- 原代码中
axios.delete前缺少await,会导致还没等删除请求完成就触发页面刷新,可能出现删除请求还没落到后端页面就刷新的问题 - 删除成功后无需调用
window.location.reload()刷新整页,可以直接过滤掉被删除的id更新前端的applications数组,体验更流畅 - 循环时外层多余的
<> </>Fragment可以删除,没有实际作用
内容的提问来源于stack exchange,提问作者crackchain
相关产品推荐
相关产品推荐

