使用 React hooks 从卡片列表中打开特定卡片的模态
Open a modal for a specific card from a list of cards using React hooks
我正在使用 API 来获取数据并将其呈现到卡片中。单击卡片时,我想为每张卡片呈现相应的模式。我正在使用 material-ui 的 React modal component(简单模态示例)。问题是,我得到的不是点击卡片的模态,而是最后一张卡片的模态。我设法在状态挂钩中“捕获”了点击卡片的数据,但我不确定如何使用它来呈现正确的组件。
这是渲染卡片的主页组件:
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>
);
};
这是 BeerCard 组件:
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>
);
};
而 BeerCardExpanded 是在 material-ui 的模态组件中注入的组件:
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>
);
});
我创建了一个codesandbox,这里是link。
您似乎将 beer.id
与地图索引混淆了。尝试使用 Id 而不是索引。
{beers.map((beer) => (
<BeerCard
key={beer.name}
beer={beer}
id={beer.id}
handleOpen={handleOpen}
/>
))}
https://codesandbox.io/s/new-tree-z3pxf
还要注意里面的过滤器 handleOpen
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>
);
我正在使用 API 来获取数据并将其呈现到卡片中。单击卡片时,我想为每张卡片呈现相应的模式。我正在使用 material-ui 的 React modal component(简单模态示例)。问题是,我得到的不是点击卡片的模态,而是最后一张卡片的模态。我设法在状态挂钩中“捕获”了点击卡片的数据,但我不确定如何使用它来呈现正确的组件。
这是渲染卡片的主页组件:
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>
);
};
这是 BeerCard 组件:
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>
);
};
而 BeerCardExpanded 是在 material-ui 的模态组件中注入的组件:
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>
);
});
我创建了一个codesandbox,这里是link。
您似乎将 beer.id
与地图索引混淆了。尝试使用 Id 而不是索引。
{beers.map((beer) => (
<BeerCard
key={beer.name}
beer={beer}
id={beer.id}
handleOpen={handleOpen}
/>
))}
https://codesandbox.io/s/new-tree-z3pxf
还要注意里面的过滤器 handleOpen
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>
);