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
相关产品推荐
相关产品推荐

