ReactJS 基于 id 映射选择数组

ReactJS mapped choices array based on id

我是 JavaScript 的新手,希望有人能帮我解决这个问题。

我有一个显示所有已安排考试的页面,当您按下“了解更多”时,会打开一个模式,您可以在其中修改有关考试本身的信息。目前它显示了创建考试时选择的设备以及可用的其余设备,您应该能够 select/deselect 以便在需要时进行更改。问题是每次检查都会打开不同的模式以仅显示相应的数据。我通过映射显示的所有考试信息都可以到达“考试”嵌套数组的内部数组,所以当我需要打开模态以获得特定信息时,我不知道如何在呈现之前初始化常量考试信息。目前我正在映射所选设备的值,这不允许我像我应该的那样更改选择。

https://codesandbox.io/s/81xer5

import "./styles.css";
import React, { useState, useEffect } from "react";
import Box from "@mui/material/Box";
import Card from "@mui/material/Card";
import CardActions from "@mui/material/CardActions";
import CardContent from "@mui/material/CardContent";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import Modal from "@mui/material/Modal";
import Chip from "@mui/material/Chip";
import OutlinedInput from "@mui/material/OutlinedInput";
import InputLabel from "@mui/material/InputLabel";
import MenuItem from "@mui/material/MenuItem";
import FormControl from "@mui/material/FormControl";
import Select from "@mui/material/Select";

const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const MenuProps = {
  PaperProps: {
    style: {
      maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
      width: 250
    }
  }
};

const style = {
  position: "absolute",
  top: "50%",
  left: "50%",
  transform: "translate(-50%, -50%)",
  width: 400,
  bgcolor: "background.paper",
  boxShadow: 24,
  p: 4,
  borderRadius: 1
};

export default function App() {
  const [exams, setExams] = useState([
    {
      id: "18897a8c-bd5b-4fc0-86d1-74ee509d46ee",
      name: "New Test",
      date: null,
      time: null,
      date2: "2022-06-20",
      time2: "15:30",
      students: [
        {
          id: "749ce920-2462-457a-8af3-26ff9c00dda5",
          username: "student1",
          email: "student1@gmail.com",
          firstName: "Student",
          lastName: "Studentov",
          roleName: "STUDENT"
        },
        {
          id: "90289548-19bb-480b-81e3-c36340debbc7",
          username: "student2",
          email: "student2@gmail.com",
          firstName: "Student",
          lastName: "Studentov",
          roleName: "STUDENT"
        },
        {
          id: "dfe50fe5-ef9d-480e-aa6c-2f5c81bb22da",
          username: "student3",
          email: "student3@gmail.com",
          firstName: "Student",
          lastName: "Studentov",
          roleName: "STUDENT"
        }
      ],
      staff: [
        {
          id: "a3b53ed0-63fc-4f77-a8dc-74915d6aefea",
          username: "staff",
          email: "staff@gmail.com",
          firstName: "Staff",
          lastName: "Staffov",
          roleName: "STAFF"
        }
      ],
      rooms: [
        {
          id: "a49f18cb-4fe8-4a2c-a665-4361c5401f31",
          number: 100,
          nrOfSeats: 20
        },
        {
          id: "5c46e888-fce4-4c1b-a8ec-e04d32a5cf6c",
          number: 400,
          nrOfSeats: 10
        }
      ],
      equipment: [
        {
          id: "08506d1b-30ce-43d2-a0b8-74f87082e356",
          name: "Crane",
          availability: true
        }
      ]
    },
    {
      id: "65b7ecd2-ba30-4369-9f13-9186dc5cc73c",
      name: "Crane Exam",
      date: null,
      time: null,
      date2: null,
      time2: null,
      students: [
        {
          id: "749ce920-2462-457a-8af3-26ff9c00dda5",
          username: "student1",
          email: "student1@gmail.com",
          firstName: "Student",
          lastName: "Studentov",
          roleName: "STUDENT"
        },
        {
          id: "90289548-19bb-480b-81e3-c36340debbc7",
          username: "student2",
          email: "student2@gmail.com",
          firstName: "Student",
          lastName: "Studentov",
          roleName: "STUDENT"
        },
        {
          id: "dfe50fe5-ef9d-480e-aa6c-2f5c81bb22da",
          username: "student3",
          email: "student3@gmail.com",
          firstName: "Student",
          lastName: "Studentov",
          roleName: "STUDENT"
        }
      ],
      staff: [
        {
          id: "a3b53ed0-63fc-4f77-a8dc-74915d6aefea",
          username: "staff",
          email: "staff@gmail.com",
          firstName: "Staff",
          lastName: "Staffov",
          roleName: "STAFF"
        }
      ],
      rooms: [
        {
          id: "a49f18cb-4fe8-4a2c-a665-4361c5401f31",
          number: 100,
          nrOfSeats: 20
        },
        {
          id: "5c46e888-fce4-4c1b-a8ec-e04d32a5cf6c",
          number: 400,
          nrOfSeats: 10
        }
      ],
      equipment: [
        {
          id: "08506d1b-30ce-43d2-a0b8-74f87082e356",
          name: "Crane",
          availability: true
        },
        {
          id: "be1da3c9-7192-459f-bdba-767e005eaac9",
          name: "Killer Robot",
          availability: true
        }
      ]
    }
  ]);

  const [equipment, setEquipment] = useState([
    {
      id: "08506d1b-30ce-43d2-a0b8-74f87082e356",
      name: "Crane",
      availability: true
    },
    {
      id: "7a1716c7-3398-4e3d-9523-7ba4a102a79b",
      name: "Lift",
      availability: true
    },
    {
      id: "be1da3c9-7192-459f-bdba-767e005eaac9",
      name: "Killer Robot",
      availability: true
    }
  ]);

  const initialShowState = Object.fromEntries(
    exams.map((data) => [data.id, false])
  );
  const [show, setShow] = React.useState(initialShowState);
  const toggleShow = (id) =>
    setShow((prev) => {
      return { ...prev, [id]: !prev[id] };
    });
  console.log({ show });

  const [value, setValue] = React.useState([]); //this is what the select chip uses by default

  const handleChange = (e) => {
    const {
      target: { value }
    } = e;

    console.log(value);
    setValue(
      // On autofill we get a the stringified value.
      typeof value === "string" ? value.split(",") : value
    );
  };

  return (
    <div className="App">
      {exams.map((data, key) => {
        return (
          <div key={key} style={{ width: "300px", display: "inline-block" }}>
            <Box
              sx={{
                minWidth: 300,
                maxWidth: 300,
                display: "inline-block",
                paddingTop: "10px",
                paddingLeft: "10px"
              }}
            >
              <Card variant="outlined">
                <React.Fragment>
                  <CardContent>
                    <Typography variant="h5" component="div">
                      {data.name}
                    </Typography>
                  </CardContent>

                  <CardActions>
                    <Button size="small" onClick={() => toggleShow(data.id)}>
                      Learn More
                    </Button>
                  </CardActions>
                </React.Fragment>
              </Card>
            </Box>

            <Modal open={show[data.id]} onClose={() => toggleShow(data.id)}>
              <Box sx={style}>
                <Typography
                  component={"span"}
                  id="transition-modal-description"
                  sx={{ mt: 2 }}
                >
                  <FormControl sx={{ m: 1, width: 300 }}>
                    <InputLabel id="demo-multiple-chip-label">Chip</InputLabel>
                    <Select
                      multiple
                      value={data.equipment.map((sub) => sub.id)}
                      // value={value}
                      onChange={handleChange}
                      input={
                        <OutlinedInput id="select-multiple-chip" label="Chip" />
                      }
                      renderValue={(selected) => {
                        return (
                          <Box
                            sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}
                          >
                            {selected.map((value) => {
                              const option = equipment.find(
                                (o) => o.id === value
                              );
                              return <Chip key={value} label={option.name} />;
                            })}
                          </Box>
                        );
                      }}
                      MenuProps={MenuProps}
                    >
                      {equipment.map((option) => (
                        <MenuItem key={option.id} value={option.id}>
                          {option.name}
                        </MenuItem>
                      ))}
                    </Select>
                  </FormControl>
                </Typography>
              </Box>
            </Modal>
          </div>
        );
      })}
    </div>
  );
}

继续我上面的评论,你在 map 函数中添加了一个 <Modal>,这将为每个考试安装一个 <Modal> 元素,这对性能和其他方面都不利难以实施。

您想要做的是只有一个模态,点击“了解更多”后您将活动考试保存在一个状态中,模态使用该状态显示正确的数据。您还想拆分考试和模态之间的逻辑,以使其更易于实施。

这里是一个示例代码,为了让代码更清晰,我将数组移到了组件之外:

const EXAMS = [...];
const EQUIPMENTS = [...];

export default function App() {
  const [exams, setExams] = useState(EXAMS);
  const [equipment, setEquipment] = useState(EQUIPMENTS);

  const [modalExam, setModalExam] = useState(null);

  return (
    <div className="App">
      {exams.map((data, key) => {
        return (
          <div key={key} style={{ width: "300px", display: "inline-block" }}>
            <Box
              sx={{
                minWidth: 300,
                maxWidth: 300,
                display: "inline-block",
                paddingTop: "10px",
                paddingLeft: "10px",
              }}
            >
              <Card variant="outlined">
                <React.Fragment>
                  <CardContent>
                    <Typography variant="h5" component="div">
                      {data.name}
                    </Typography>
                  </CardContent>

                  <CardActions>
                    <Button size="small" onClick={() => setModalExam(data)}>
                      Learn More
                    </Button>
                  </CardActions>
                </React.Fragment>
              </Card>
            </Box>
          </div>
        );
      })}

      <ModalExam
        equipment={equipment}
        exam={modalExam}
        onClose={() => setModalExam(null)}
      />
    </div>
  );
}

function ModalExam({ exam, equipment, onClose }) {
  const [chipValue, setChipValue] = useState([]);

  useEffect(() => {
    if (exam) {
      setChipValue(exam.equipment.map((sub) => sub.id));
    }
  }, [exam]);

  const handleChange = (e) => {
    const {
      target: { value },
    } = e;

    console.log(value);

    setChipValue(typeof value === "string" ? value.split(",") : value);
  };

  return (
    <Modal open={exam !== null} onClose={onClose}>
      {exam && (
        <Box sx={style}>
          <Typography
            component={"span"}
            id="transition-modal-description"
            sx={{ mt: 2 }}
          >
            <p>{exam.name}</p>

            <FormControl sx={{ m: 1, width: 300 }}>
              <InputLabel id="demo-multiple-chip-label">Chip</InputLabel>
              <Select
                multiple
                value={chipValue}
                // value={value}
                onChange={handleChange}
                input={<OutlinedInput id="select-multiple-chip" label="Chip" />}
                renderValue={(selected) => {
                  return (
                    <Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
                      {selected.map((value) => {
                        const option = equipment.find((o) => o.id === value);
                        return <Chip key={value} label={option.name} />;
                      })}
                    </Box>
                  );
                }}
                MenuProps={MenuProps}
              >
                {equipment.map((option) => (
                  <MenuItem key={option.id} value={option.id}>
                    {option.name}
                  </MenuItem>
                ))}
              </Select>
            </FormControl>
          </Typography>
        </Box>
      )}
    </Modal>
  );
}

看看拆分逻辑后它变得多么简单。这是沙箱:https://codesandbox.io/s/hedk9g