React MUI卡片展开异常:点击整列同步展开,需求单张独立展开
问题描述
我通过循环从数据库获取数据生成标签页卡片,点击「查看更多」按钮时,整列可见卡片会同步展开;只有行内仅一张卡片时,才能正常展开对应单张卡片。我需要实现每次仅展开一张卡片的效果。
解决方案
要解决这个问题,需要将卡片的展开状态从子组件提升到父组件统一管理,通过控制唯一的展开卡片ID来实现单张展开的效果:
- 父组件维护当前展开的卡片ID,子组件根据该ID判断自身是否需要展开
- 点击按钮时,更新父组件的展开ID,确保同一时间只有一个卡片处于展开状态
修改后的完整代码
import React, { useState } from "react"; import { Box, Card, CardActions, CardContent, Collapse, Button, Typography, Rating, useTheme, useMediaQuery, } from "@mui/material"; import Header from "../../components/Header"; import { useGetProductsQuery } from "../../state/api"; import LinearProgress from "@mui/material/LinearProgress"; const Product = ({ _id, name, description, price, rating, category, supply, stat, isExpanded, handleExpand }) => { const theme = useTheme(); return ( <Card sx={{ backgroundImage: "none", backgroundColor: theme.palette.background.alt, borderRadius: "0.55rem", }} > <CardContent> <Typography sx={{ fontSize: 14 }} color={theme.palette.secondary[700]} gutterBottom > {category} </Typography> <Typography variant="h5" component="div"> {name} </Typography> <Typography sx={{ mb: "1.5rem" }} color={theme.palette.secondary[400]}> ${Number(price).toFixed(2)} </Typography> <Rating value={rating} readOnly /> <Typography variant="body2">{description}</Typography> </CardContent> <CardActions> <Button variant="primary" size="small" onClick={() => handleExpand(_id)} sx={{ backgroundColor: theme.palette.secondary[600] }} > {isExpanded ? "收起" : "查看更多"} </Button> </CardActions> <Collapse in={isExpanded} timeout="auto" unmountOnExit sx={{ color: theme.palette.neutral[300], }} > <CardContent> <Typography>id: {_id}</Typography> <Typography>剩余库存: {supply}</Typography> <Typography> 今年年度总销售额: {stat[0].yearlySalesTotal} </Typography> <Typography> 今年年度总销量: {stat[0].yearlyTotalSoldUnits} </Typography> </CardContent> </Collapse> </Card> ); }; const Products = () => { const { data, isLoading } = useGetProductsQuery(); const isNonMobile = useMediaQuery("(min-width: 1000px)"); const [expandedCardId, setExpandedCardId] = useState(null); const handleExpand = (id) => { // 如果当前卡片已展开则收起,否则展开该卡片并关闭其他卡片 setExpandedCardId(expandedCardId === id ? null : id); }; return ( <Box m="1.5rem 2.5rem"> <Header title="产品列表" subtitle="查看你的全部产品" /> {data && !isLoading ? ( <Box mt="20px" display="grid" gridTemplateColumns="repeat(4, minmax(0, 1fr))" justifyContent="space-between" rowGap="20px" columnGap="1.33%" sx={{ "& > div": { gridColumn: isNonMobile ? undefined : "span 4" }, }} > {data.map( ({ _id, name, description, price, rating, category, supply, stat, }) => ( <Product key={_id} _id={_id} name={name} description={description} price={price} rating={rating} category={category} supply={supply} stat={stat} isExpanded={expandedCardId === _id} handleExpand={handleExpand} /> ) )} </Box> ) : ( <LinearProgress color="primary" fourcolor="false" variant="indeterminate" /> )} </Box> ); }; export default Products;
关键修改点说明
- 状态提升:将原本
Product组件内部的isExpanded状态移到父组件Products中,改为expandedCardId存储当前展开的卡片ID - 统一控制:点击「查看更多」时,通过父组件的
handleExpand方法更新expandedCardId,确保同一时间只有一个卡片处于展开状态 - 状态传递:父组件向每个
Product传递isExpanded(判断当前卡片是否匹配展开ID)和handleExpand方法 - 交互优化:按钮文本根据展开状态切换为「查看更多」或「收起」,提升用户体验
内容的提问来源于stack exchange,提问作者Abdullah Web-dev
相关产品推荐
相关产品推荐

