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

React遍历数组时点击按钮始终返回最后一项ID的问题求助

React组件调用cancelItem始终传入最后一项item_id的问题解决

开发React组件时,期望点击列表项的Cancel按钮打开确认弹窗,点击Yes调用cancelItem时传入对应项的item_id,但实际总是传入数组最后一项的id,推测和作用域相关但未找到解决方案。

最小复现代码

import * as React from "react";
import {
  Typography,
  Button,
  Dialog,
  DialogActions,
  DialogContent,
  DialogContentText,
  DialogTitle,
  ListItem,
  ListItemText,
  ListItemButton,
  Box
} from "@mui/material";

interface IItem {
  item_id: number;
  name: string;
}

interface ItemEditModel {
  itemId: number;
}

let items: IItem[] = [
  { item_id: 0, name: "first item" },
  { item_id: 1, name: "second item" },
  { item_id: 3, name: "third item" }
];

export default function App() {
  const [open, setOpen] = React.useState(false);
  const cancelItem = (item_id: number) => {
    let newItem: ItemEditModel = { itemId: item_id };
    console.log(newItem);
    setOpen(false);
    // do cancel item
  };
  const handleClickOpen = () => {
    setOpen(true);
  };
  const handleClose = () => {
    setOpen(false);
  };

  return (
    <>
      {items.map((item: IItem, index: number) => (
        <Box key={index}>
          <ListItem>
            <Typography>
              {" "}
              <b>Item {item.name}</b>
            </Typography>
            <ListItemButton onClick={handleClickOpen}>
              <ListItemText primary="Cancel" />
            </ListItemButton>
            <Dialog
              open={open}
              onClose={handleClose}
              aria-labelledby="alert-dialog-title"
              aria-describedby="alert-dialog-description"
            >
              <DialogTitle id="alert-dialog-title">Cancel</DialogTitle>
              <DialogContent>
                <DialogContentText id="alert-dialog-description">
                  Are you sure?
                </DialogContentText>
              </DialogContent>
              <DialogActions>
                <Button variant="contained" onClick={handleClose}>
                  No
                </Button>
                <Button
                  variant="contained"
                  onClick={() => cancelItem(item.item_id)}
                  autoFocus
                >
                  Yes
                </Button>
              </DialogActions>
            </Dialog>
            <ListItemButton onClick={() => console.log(item.item_id)}>
              <ListItemText primary="Edit" />
            </ListItemButton>
          </ListItem>
        </Box>
      ))}
    </>
  );
}

问题原因

你在循环中为每个列表项都渲染了一个Dialog组件,并且所有Dialog共享同一个open状态。当点击任意一个Cancel按钮时,所有Dialog都会被设置为打开状态,但由于DOM层级的原因,视觉上只会显示最后一个Dialog。此时点击Yes按钮,实际触发的是最后一个Dialog的点击事件,自然会传入最后一项的item_id。这和作用域无关,是多Dialog复用同一状态导致的逻辑错误。

解决方案

只渲染一个全局的Dialog,用状态存储当前要操作的item_id,具体步骤如下:

  • 新增状态currentCancelItemId,用于存储当前要取消的项的id;
  • 点击Cancel按钮时,同时设置currentCancelItemId为当前项的item_id并打开弹窗;
  • 弹窗的Yes按钮直接使用currentCancelItemId调用cancelItem;
  • 关闭弹窗时重置currentCancelItemId。

修改后的代码示例:

import * as React from "react";
import {
  Typography,
  Button,
  Dialog,
  DialogActions,
  DialogContent,
  DialogContentText,
  DialogTitle,
  ListItem,
  ListItemText,
  ListItemButton,
  Box
} from "@mui/material";

interface IItem {
  item_id: number;
  name: string;
}

interface ItemEditModel {
  itemId: number;
}

let items: IItem[] = [
  { item_id: 0, name: "first item" },
  { item_id: 1, name: "second item" },
  { item_id: 3, name: "third item" }
];

export default function App() {
  const [open, setOpen] = React.useState(false);
  const [currentCancelItemId, setCurrentCancelItemId] = React.useState<number | null>(null);

  const cancelItem = () => {
    if (currentCancelItemId === null) return;
    let newItem: ItemEditModel = { itemId: currentCancelItemId };
    console.log(newItem);
    setOpen(false);
    setCurrentCancelItemId(null);
    // do cancel item
  };

  const handleClickOpen = (itemId: number) => {
    setCurrentCancelItemId(itemId);
    setOpen(true);
  };

  const handleClose = () => {
    setOpen(false);
    setCurrentCancelItemId(null);
  };

  return (
    <>
      {items.map((item: IItem, index: number) => (
        <Box key={index}>
          <ListItem>
            <Typography>
              {" "}
              <b>Item {item.name}</b>
            </Typography>
            <ListItemButton onClick={() => handleClickOpen(item.item_id)}>
              <ListItemText primary="Cancel" />
            </ListItemButton>
            <ListItemButton onClick={() => console.log(item.item_id)}>
              <ListItemText primary="Edit" />
            </ListItemButton>
          </ListItem>
        </Box>
      ))}
      {/* 只渲染一个全局Dialog */}
      <Dialog
        open={open}
        onClose={handleClose}
        aria-labelledby="alert-dialog-title"
        aria-describedby="alert-dialog-description"
      >
        <DialogTitle id="alert-dialog-title">Cancel</DialogTitle>
        <DialogContent>
          <DialogContentText id="alert-dialog-description">
            Are you sure?
          </DialogContentText>
        </DialogContent>
        <DialogActions>
          <Button variant="contained" onClick={handleClose}>
            No
          </Button>
          <Button
            variant="contained"
            onClick={cancelItem}
            autoFocus
          >
            Yes
          </Button>
        </DialogActions>
      </Dialog>
    </>
  );
}

内容的提问来源于stack exchange,提问作者Sachihiro

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 02:30:54