My react component returns data from my Firestore DB and maps the data it on Material-UI cards. However, when I press the ExpandMoreIcon, it opens EVERY card. I just want to open each card individually. I know the solution has to do with useState function for expanded & setExpanded.
I've tried to fix this bug but I cant seem to make it work. Any help would be greatly appreciated.
export const NFTprojects = () => {
const [expanded, setExpanded] = useState(false);
const handleExpandClick = (id) => {
setExpanded(!expanded)
};
const [projects, setProjects] = useState([]);
const ref = firebase.firestore().collection("NFTprojects");
function getProjects() {
ref.onSnapshot((querySnapshot) => {
const items = []; //initiliaze empty array
querySnapshot.forEach((doc) => {
items.push(doc.data());
});
setProjects(items);
});
}
useEffect(() => {
getProjects();
}, []);
return (
<div>
<Grid container spacing={4} direction="row" justifyContent="flex-start" alignItems="flex-start">
{projects.map((project) => (
<Grid item xs={4}>
<Card sx={{ maxWidth: 400, borderRadius: 3, mb: 5 }}>
<CardMedia
component="img"
height="140"
image={project.imageUrl}
alt={project.projectName}
/>
<CardContent>
<Typography gutterBottom variant="h5" sx={{ fontWeight: 'bold' }}>
{project.projectName}
</Typography>
<Typography variant="h6" gutterBottom component="div" fontWeight="bold">
{project.jobPosition}
</Typography>
<Typography variant="body2" color="text.secondary" style={{ fontFamily: 'Merriweather' }}>
{project.projectDesc}
</Typography>
</CardContent>
<CardActions disableSpacing>
<Tooltip title="Website">
<IconButton aria-label="secondary marketplace" href={project.websiteLink} target="_blank">
<WebsiteIcon />
</IconButton>
</Tooltip>
<Tooltip title="Twitter">
<IconButton aria-label="twitter" href={project.twitterLink} target="_blank">
<TwitterIcon />
</IconButton>
</Tooltip>
<Tooltip title="Secondary">
<IconButton aria-label="Secondary market link" href={project.secondaryMarket} target="_blank">
<ShoppingCartIcon />
</IconButton>
</Tooltip>
<Tooltip title="Discord">
<IconButton aria-label="discord" href={project.discordLink} target="_blank">
<SvgIcon component={DiscordIcon} viewBox="0 0 600 476.6" />
</IconButton>
</Tooltip>
<Button size="small" variant="contained" sx={{ ml: 15, backgroundColor: 'black' }}>Apply</Button>
<ExpandMore
expand={expanded}
onClick={handleExpandClick}
aria-expanded={expanded}
aria-label="show more"
>
<ExpandMoreIcon />
</ExpandMore>
</CardActions>
<Collapse in={expanded} 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>
))}
</Grid>
</div >
);
}
One approach is to create a separate component for the card. This will enable you to add states to the component and control them. Here is a minimal example demonstrating how you can approach it.
import React, { useState } from "react";
// this is just sample data to work with - equivalent to the data you get from Firebase
const sampleCardsArray = [
{
id: 0,
name: "Card 1",
color: "red",
description: "This is card 1",
},
{
id: 1,
name: "Card 2",
color: "blue",
description: "This is card 2",
},
{
id: 2,
name: "Card 3",
color: "green",
description: "This is card 3",
},
];
// component for all cards
export const AllCards = () => {
// this state is used to store the INDEX of the card that is currently expanded
const [expandedCard, setExpandedCard] = useState(null);
return (
<div>
{sampleCardsArray.map((card, index) => (
<OneCard
card={card}
key={card.id}
// this prop passes the boolean value of whether the card is expanded or not
isExpanded={expandedCard === index}
// this prop receives the index of the card that is expanded and sets the state
expandCard={() => setExpandedCard(index)}
/>
))}
</div>
);
};
// component for one card
// We only show the fields: name and color. We show the description when the card is clicked
export const OneCard = ({ card, isExpanded, expandCard }) => {
return (
<div>
<h1>{card.name}</h1>
<h2>{card.color}</h2>
{
// showing expand button only when card is not expanded
}
{isExpanded ? (
<p>{card.description}</p>
) : (
<button onClick={() => expandCard()}>Expand card</button>
)}
</div>
);
};