I am using an API to fetch and render the data it into cards. I want to render a corresponding modal for each card when the card is clicked. I am using the React modal component by material-ui (the simple modal example). The problem is that instead of the modal for the clicked card, I’m getting the modal for the last card. I managed to “capture” the data of the clicked card in a state hook, but I’m not sure how to use that to render the correct component.
This is the Home component where the cards are rendered:
import React, { useState, useEffect } from 'react'; import axios from 'axios'; import { BeerCardExpanded } from './BeerCardExpanded'; import { BeerCard } from './BeerCard'; import { makeStyles } from '@material-ui/core/styles'; import Modal from '@material-ui/core/Modal'; import '../styles/Home.css'; import { DialogContent } from '@material-ui/core'; function rand() { return Math.round(Math.random() * 20) - 10; } function getModalStyle() { const top = 50 + rand(); const left = 50 + rand(); return { top: `${top}%`, left: `${left}%`, transform: `translate(-${top}%, -${left}%)`, }; } const useStyles = makeStyles(theme => ({ paper: { position: 'absolute', width: 400, backgroundColor: theme.palette.background.paper, border: '2px solid #000', boxShadow: theme.shadows[5], padding: theme.spacing(2, 4, 3), }, })); export const Home = () => { const ref = React.createRef(); const classes = useStyles(); const [modalStyle] = React.useState(getModalStyle); const [beers, setBeers] = useState([]); const [open, setOpen] = useState(false); const [isClicked, setIsClicked] = useState([]); const fetchBeerData = async () => { try { const { data } = await axios.get( 'https://api.punkapi.com/v2/beers?per_page=9' ); console.log(data); return data; } catch (err) { console.log(err); } }; useEffect(() => { fetchBeerData().then(data => { setBeers(data); }); }, []); const handleOpen = id => { setIsClicked(isClicked.push(beers.filter(item => item.id === id))); setIsClicked(id); setOpen(true); console.log(isClicked[0]); }; const handleClose = () => { setOpen(false); setIsClicked([]); }; return ( <div className='beer-container'> {beers.map((beer, index) => ( <> <BeerCard key={beer.name} beer={beer} id={index} handleOpen={handleOpen} /> <Modal aria-labelledby='transition-modal-title' aria-describedby='transition-modal-description' open={open} onClose={handleClose} > <DialogContent> <BeerCardExpanded id={`${isClicked.id}-${isClicked.name}`} className={classes.paper} style={modalStyle} beer={beer} ref={ref} /> </DialogContent> </Modal> </> ))} </div> ); };
This is the BeerCard component:
import React, { useState } from 'react'; import '../styles/BeerCard.css'; import CardActions from '@material-ui/core/CardActions'; import FavoriteBorderIcon from '@material-ui/icons/FavoriteBorder'; import FavoriteIcon from '@material-ui/icons/Favorite'; import IconButton from '@material-ui/core/IconButton'; export const BeerCard = ({ beer, handleOpen }) => { const [isFavorite, setIsFavorite] = useState(false); const handleIconClick = e => { e.stopPropagation(); setIsFavorite(!isFavorite); }; return ( <div className='card' onClick={() => handleOpen(beer.id)}> <div className='image-container'> <img src={beer.image_url} alt={beer.name} /> </div> <div className='info-container'> <section className='name-tagline'> <p className='beer-name'>{beer.name}</p> <p className='beer-tagline'> <i>{beer.tagline}</i> </p> </section> <p className='beer-description'>{beer.description}</p> </div> <CardActions className='favorite-icon'> <IconButton aria-label='add to favorites'> {!isFavorite ? ( <FavoriteBorderIcon onClickCapture={e => handleIconClick(e)} /> ) : ( <FavoriteIcon onClickCapture={e => handleIconClick(e)} /> )} </IconButton> </CardActions> </div> ); };
And the BeerCardExpanded is the component that gets injected in the material-ui’s modal component:
import React, { useState } from 'react'; import CardActions from '@material-ui/core/CardActions'; import FavoriteBorderIcon from '@material-ui/icons/FavoriteBorder'; import FavoriteIcon from '@material-ui/icons/Favorite'; import IconButton from '@material-ui/core/IconButton'; import '../styles/BeerCardExpanded.css'; export const BeerCardExpanded = React.forwardRef(({ beer }, ref) => { const [isFavorite, setIsFavorite] = useState(false); const handleIconClick = e => { e.stopPropagation(); setIsFavorite(!isFavorite); }; return ( <div className='beer-card' ref={ref}> <CardActions className='favorite-icon'> <IconButton aria-label='add to favorites'> {!isFavorite ? ( <FavoriteBorderIcon onClickCapture={e => handleIconClick(e)} /> ) : ( <FavoriteIcon onClickCapture={e => handleIconClick(e)} /> )} </IconButton> </CardActions> <section className='top'> <section className='image-name-tagline'> {<img src={beer.image_url} alt={beer.name} />} <section className='right-side'> <p className='beer-name'>{beer.name}</p> <p className='beer-tagline'> <i>{beer.tagline}</i> </p> </section> </section> <p className='beer-description'>{beer.description}</p> </section> <section> <p className='food-pairing'> <i> Pairs best with: {beer.food_pairing.map((item, index) => ( <span key={(item, index)}> ☆{item} </span> ))} </i> </p> </section> </div> ); });
I created a codesandbox, here’s the link.
Answer
Looks like you were getting the beer.id
mixed up with the map index. Try using the Id rather than index.
{beers.map((beer) => ( <BeerCard key={beer.name} beer={beer} id={beer.id} handleOpen={handleOpen} /> ))}
https://codesandbox.io/s/new-tree-z3pxf
Notice the filter inside handleOpen
also
useEffect(() => { const fetchBeerData = async () => { try { const { data } = await axios.get( "https://api.punkapi.com/v2/beers?per_page=9" ); setBeers(data); } catch (err) { console.log(err); } }; fetchBeerData(); }, []); const handleOpen = (id) => { setIsClicked(beers.find(x => x.id === id)); setOpen(true); }; const handleClose = () => { setOpen(false); setIsClicked({}); }; return ( <div className="beer-container"> {beers.map((beer) => ( <BeerCard key={beer.name} beer={beer} id={beer.id} handleOpen={handleOpen} /> ))} <Modal aria-labelledby="transition-modal-title" aria-describedby="transition-modal-description" open={open} onClose={handleClose} > <DialogContent> <BeerCardExpanded id={`${isClicked.id}-${isClicked.name}`} className={classes.paper} style={modalStyle} beer={isClicked} ref={ref} /> </DialogContent> </Modal> </div> );