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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 14:57:01