ReactJS map渲染列表时如何通过onClick单独控制单个卡片展开
问题根源
所有卡片共用了同一个布尔类型的expanded状态,状态变更时所有卡片的展开判断逻辑同步生效,自然会出现全展开/全收起的问题,无法区分单个卡片的独立状态。
修复步骤
1. 调整状态定义与数据获取逻辑
不再用布尔值存全局展开状态,改为存储当前展开卡片的唯一ID,同时在拉取Firestore数据时把文档ID注入到项目数据中,作为每个卡片的唯一标识:
export const NFTprojects = () => { // 存储当前展开的卡片ID,默认值为null即无卡片展开 const [expandedId, setExpandedId] = useState(null); const [projects, setProjects] = useState([]); const ref = firebase.firestore().collection("NFTprojects"); function getProjects() { ref.onSnapshot((querySnapshot) => { const items = []; querySnapshot.forEach((doc) => { // 注入文档唯一ID items.push({ id: doc.id, ...doc.data() }); }); setProjects(items); }); } useEffect(() => { getProjects(); }, []); // 展开点击逻辑:点击已展开的卡片则收起,否则展开当前点击卡片 const handleExpandClick = (projectId) => { setExpandedId(expandedId === projectId ? null : projectId); }; // 其余return渲染逻辑按后续步骤修改
2. 修改渲染部分的展开判断逻辑
在map渲染卡片时,把原来直接绑定全局expanded的地方,改为判断当前卡片ID和expandedId是否一致,同时点击事件传入当前卡片ID:
{projects.map((project) => ( <Grid item xs={4} key={project.id}> <Card sx={{ maxWidth: 400, borderRadius: 3, mb: 5 }}> {/* 上方CardMedia、CardContent、CardActions里的IconButton、Button部分代码保持不变 */} <ExpandMore expand={expandedId === project.id} onClick={() => handleExpandClick(project.id)} aria-expanded={expandedId === project.id} aria-label="show more" > <ExpandMoreIcon /> </ExpandMore> </CardActions> {/* Collapse的in属性同样改为ID判断 */} <Collapse in={expandedId === project.id} timeout="auto" unmountOnExit> <CardContent> <Typography variant="h6" sx={{ fontWeight: 'bold' }} style={{ fontFamily: 'Merriweather' }}>Job Description:</Typography> <Typography paragraph> {project.jobDesc} </Typography> <Typography variant="h6" sx={{ fontWeight: 'bold' }}>Prerequisites</Typography> <Typography paragraph> {project.jobPrereq} </Typography> </CardContent> </Collapse> </Card> </Grid> ))}
逻辑说明
- 修改后同一时间只会展开一个卡片,点击其他卡片会自动收起之前展开的卡片,展开当前点击项
- 如果需要支持多卡片同时展开,只需要把
expandedId改为数组类型状态,点击时做ID的增删判断即可 - 给循环渲染的
Grid项加上key={project.id},可避免React渲染告警
内容的提问来源于stack exchange,提问作者carbon
相关产品推荐
相关产品推荐

