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

点击按钮打开Modal始终显示数组最后一个对象值的技术问题

解决点击卡片按钮打开Modal始终显示最后一个对象值的问题

问题根源

你遇到的问题是因为所有卡片中的Modal共享同一个open状态变量,而且循环结束后book变量会指向数组的最后一个元素。当你点击任意按钮打开Modal时,虽然视觉上只显示一个Modal,但实际上所有Modal都会尝试渲染,而此时book已经是最后一个对象,所以无论点哪个卡片,显示的都是最后一本书的数据。

解决方案一:用状态存储当前选中的书籍

这个方案不需要拆分组件,只需在父组件中维护当前选中的书籍对象:

  1. 修改状态定义,将原来的open替换为selectedBook:
const [selectedBook, setSelectedBook] = useState(null);
  1. 更新打开和关闭Modal的函数:
const handleOpen = (book) => {
  setSelectedBook(book);
};

const handleClose = () => {
  setSelectedBook(null);
};
  1. 修改循环中的按钮点击事件和Modal属性:
<Grid container spacing={{ xs: 2, md: 3 }} >
  {postData.posts.map((book) => (
    <Grid key={book.id}> {/* 记得加唯一key,避免React警告 */}
      <Card>
        {/* ... 卡片其他内容保持不变 ... */}
        <CardActions>
          <Button>Buy Now</Button>
          <div>
            <Button onClick={() => handleOpen(book)}>Open modal</Button>
            <Modal
              open={!!selectedBook}
              onClose={handleClose}
              aria-labelledby="modal-modal-title"
              aria-describedby="modal-modal-description"
            >
              {selectedBook && (
                <Box sx={style}>
                  <Typography id="modal-modal-title" variant="h6" component="h2">
                    {selectedBook.title}
                  </Typography>
                  <Typography>{selectedBook.author}</Typography>
                  <Typography id="modal-modal-description" sx={{ mt: 2 }}>
                    {selectedBook.detail}
                  </Typography>
                  <Rating name="size-medium" defaultValue={selectedBook.rating} />
                </Box>
              )}
            </Modal>
          </div>
        </CardActions>
      </Card>
    </Grid>
  ))}
</Grid>

解决方案二:将卡片封装为独立组件

把每个卡片和对应的Modal封装成单独的组件,每个组件维护自己的open状态,彻底避免状态共享问题:

  1. 创建BookCard组件:
const BookCard = ({ book, style }) => {
  const [open, setOpen] = useState(false);

  const handleOpen = () => setOpen(true);
  const handleClose = () => setOpen(false);

  return (
    <Card>
      <CardMedia
        component="img"
        height="240"
        image={`${book.url}`}
        alt="book cover"
      />
      <CardContent>
        <Typography gutterBottom variant="h5" component="div">
          {book.title}
        </Typography>
        <Typography>{book.author}</Typography>
      </CardContent>
      <CardActions>
        <Button>Buy Now</Button>
        <div>
          <Button onClick={handleOpen}>Open modal</Button>
          <Modal
            open={open}
            onClose={handleClose}
            aria-labelledby="modal-modal-title"
            aria-describedby="modal-modal-description"
          >
            <Box sx={style}>
              <Typography id="modal-modal-title" variant="h6" component="h2">
                {book.title}
              </Typography>
              <Typography>{book.author}</Typography>
              <Typography id="modal-modal-description" sx={{ mt: 2 }}>
                {book.detail}
              </Typography>
              <Rating name="size-medium" defaultValue={book.rating} />
            </Box>
          </Modal>
        </div>
      </CardActions>
    </Card>
  );
};
  1. 在父组件中循环渲染BookCard:
<Grid container spacing={{ xs: 2, md: 3 }} >
  {postData.posts.map((book) => (
    <Grid key={book.id}>
      <BookCard book={book} style={style} />
    </Grid>
  ))}
</Grid>

注意事项

  • 记得给循环中的每个Grid元素添加唯一的key属性(比如用book.id),这是React列表渲染的最佳实践,避免不必要的重渲染和警告。
  • 如果使用方案一,Modal内部要加selectedBook &&的判断,防止初始状态为null时出现渲染错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 10:50:29