使用 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>
  );