react-hooksbootstrap-modalreact-bootstrapbootstrap-cards

populate cards and modals via same json file using react(-bootstrap)?


The data from workData fills <Card></Card> correctly.

The <Modal></Modal> only fills with the last entry of workData (e.g. Test4, Modal4, test text 4...)

my goal is to generate cards and respective modals (for each card) using the data from the json, in the same file.

why is the modal only being filled by the last properties in the json? how do i get it to populate with the entire array? if possible please explain why this does not work the way it is.

if it's not obvious im super new, i am, any responses would be super appreciated. ty

cards good

after clicking "Read1" bad, should say Test1, test text 1

in App.js: import { Works } from "./Works";

in Works.js: import { workData } from "./data";

also in Work.js:

export const Works = () => {
  const [show, setShow] = React.useState(false);
  const onClick = () => setShow(true);
  return (
  <>
    <div className="work-container">
    <Row xs={1} md={2} lg={4} className="g-4">
        {workData.map((data, key) => {
              return (
                    <div key={key}>
                        <Col>
                            <Card>
                                <Card.Img variant="top" src={data.projectImage} />
                                <Card.Body>
                                    <Card.Title>{data.projectTitle}</Card.Title>
                                    <Card.Text>with {data.projectTeam}</Card.Text>
                                    <Button variant="link" onClick={onClick}>
                                    {data.readMore}
                                    </Button>
                                </Card.Body>
                                <Card.Footer>{data.tags}</Card.Footer>
                            </Card>
                        </Col>

                        <Modal
                        show={show}
                        onHide={() => setShow(false)}
                        dialogClassName="modal-95w"
                        >
                            <Modal.Header closeButton>
                            <Modal.Title>{data.projectTitle}</Modal.Title>
                            </Modal.Header>
                            <Modal.Body>
                                <Image src={data.projectImage}></Image>
                                <p>
                                  {data.modalText}
                                </p>
                                <Image src={data.modalImage}></Image>
                            </Modal.Body>
                        </Modal>
                    </div>
              );
        })}
    </Row>
    </div>
  </>
  );
}

in data.js:

export const workData = [
  {
    projectTitle: "Test1",
    modalTitle: "Modal1",
    modalText: "test text 1",
    modalImage: "image",
    readMore: "Read1",
    projectImage: "image",
    projectTeam: "Test1",
    year: "2022",
    link1: "link",
    link2: "link2",
    tags: [
      "#tag1 ",
      "#tag2 "
    ]
  },
...

The data from workData fills <Card></Card> correctly.

The <Modal></Modal> only fills with the last entry of workData (e.g. Test4, Modal4, test text 4...)

my goal is to generate cards and respective modals (for each card) using the data from the json, in the same file.

why is the modal only being filled by the last properties in the json? how do i get it to populate with the entire array? if possible please explain why this does not work the way it is.

cards good

after clicking "Read1" bad, should say Test1, test text 1


Solution

  • You iterate over workData for Cards and Modals, but you use only one single state for everything. What you need to do, is to also create a state for every Modal. Usually you create an array with unique id as key and boolean value. I assumed projectTitle is unique:

    {
        Test1: false,
        Test2: false,
        Test3: false
    }
    

    Because you don't know the length of your data, you just iterate over the array, as you have done for Cards und Modals:

     const initialShowState = Object.fromEntries(
        workData.map((data) => [data.projectTitle, false])
      );
      const [show, setShow] = React.useState(initialShowState);
    

    Then you need to create a generic callback function, which takes the id of the Card and shows the appropriate Modal. I simplified the logic and created a toggle function:

    const toggleShow = (id) =>
        setShow((prev) => {
          return { ...prev, [id]: !prev[id] };
        });
    

    Finally, when you render UI and iterate over workData, you need to apply the callback function to Button onClick and Modal onHide event handlers and set the show property of Modal:

    <Button variant="link" onClick={() => toggleShow(data.projectTitle)}>
    ...
    <Modal
        show={show[data.projectTitle]}
        onHide={() => toggleShow(data.projectTitle)}
        dialogClassName="modal-95w"
    >
    

    That's it. Here is the working sandbox: https://codesandbox.io/s/hungry-sunset-t865t3

    Some general tips: