Home > Software engineering >  ReactJS mapped choices array based on id
ReactJS mapped choices array based on id

Time:05-31

I am pretty new to JavaScript and was hoping someone could help me out with this one.

I have this page that shows all the scheduled exams and when you press on "Learn more" a modal opens up that should let you modify information about the exam itself. At the moment it shows the equipment that is selected when creating the exam plus the rest of the equipment available and you should be able to select/deselect in order to change if needed. The problem is that a different modal opens up for each exam to show the corresponding data only. All the exam information I've got shown through mapping to get to the inside arrays of the "exams" nested array, so I do not know how to initialize a const before the render when I need the modal to be open to get that specific exams' info. At the moment I am mapping the values of the selected equipment which does not let me change the selection like I should.

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: "[email protected]",
          firstName: "Student",
          lastName: "Studentov",
          roleName: "STUDENT"
        },
        {
          id: "90289548-19bb-480b-81e3-c36340debbc7",
          username: "student2",
          email: "[email protected]",
          firstName: "Student",
          lastName: "Studentov",
          roleName: "STUDENT"
        },
        {
          id: "dfe50fe5-ef9d-480e-aa6c-2f5c81bb22da",
          username: "student3",
          email: "[email protected]",
          firstName: "Student",
          lastName: "Studentov",
          roleName: "STUDENT"
        }
      ],
      staff: [
        {
          id: "a3b53ed0-63fc-4f77-a8dc-74915d6aefea",
          username: "staff",
          email: "[email protected]",
          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: "[email protected]",
          firstName: "Student",
          lastName: "Studentov",
          roleName: "STUDENT"
        },
        {
          id: "90289548-19bb-480b-81e3-c36340debbc7",
          username: "student2",
          email: "[email protected]",
          firstName: "Student",
          lastName: "Studentov",
          roleName: "STUDENT"
        },
        {
          id: "dfe50fe5-ef9d-480e-aa6c-2f5c81bb22da",
          username: "student3",
          email: "[email protected]",
          firstName: "Student",
          lastName: "Studentov",
          roleName: "STUDENT"
        }
      ],
      staff: [
        {
          id: "a3b53ed0-63fc-4f77-a8dc-74915d6aefea",
          username: "staff",
          email: "[email protected]",
          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>
  );
}

CodePudding user response:

Continuing my comment above, you're adding a <Modal> inside the map function, this will mount a <Modal> element for each exam, which is bad for performance and more difficult to implement.

What you want to do is to have only one modal, and upon clicking "Learn More" you save the active exam in a state, the modal uses this state to show the correct data. You also want to split the logic between the exam and the modal to make it more easy to implement.

Here is a sample code, I have moved the arrays outside the component to make the code more clear:

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>
  );
}

See how much simple it gets when you split the logic. Here is the sandbox: https://codesandbox.io/s/hedk9g

  • Related