Search code examples
reactjsmodal-dialogbootstrap-modal

React-Bootstrap modal only take last item of map


I'm a beginner in coding. I'm creating my graphic designer portfolio. I've put all the portfolio content (thumbnail, client name, description...) into an array called "portfolio" in a JSON file. Each item the array is a different project. I display a galery of thumbnails, and when I click on a thumbnail, a modal opens with the project details.

I map on my "portfolio" array to display the galery, that works. But when I open the modal, it always display the last item of my array.

import React from 'react';
import Modal from 'react-bootstrap/Modal';

class Portfolio extends React.Component {
  constructor(props, context) {
    super(props, context);
    this.state = {
      error: null,
      isLoaded: false,
      items: [],
      projectName: "",
      show: false
    };

    this.handleShow = () => {
      this.setState({ show: true });
    };

    this.handleClose = () => {
      this.setState({ show: false });
    };
  }

  componentDidMount() {
    fetch("https://api.myjson.com/bins/1cbaj5")
      .then(res => res.json())
      .then(
        (result) => {
          this.setState({
            isLoaded: true,
            items: result.portfolio
          });
        },
        (error) => {
          this.setState({
            isLoaded: true,
            error
          });
        }
      )
  }
    
  render() {
    const {error, isLoaded, items} = this.state;
    if (error) {
      return <div>Erreur : {error.message}</div>;
    } else if (!isLoaded) {
      return <div>Chargement…</div>;
    } else {
      return (
        <div id="portfolio">
          <h2>Portfolio</h2>
          <ul className="portfolio-list">
            {items.map(item => (
              <li key={item.client}>
                <div className="vignette">

                  <button onClick={() => this.handleShow()}>
                    <h4>{item.client}</h4>
                    <div className="filtre"></div>
                    <img src={item.vignette} alt={item.titre} />
                  </button>

                  <Modal show={this.state.show} onHide={this.handleClose}>
                    <Modal.Header closeButton>
                      <h3>{item.client}</h3>
                    </Modal.Header>
                    <Modal.Body>
                      <p>{item.description}</p>
                    </Modal.Body>
                  </Modal>

                </div>
              </li>
            ))}
          </ul>
        </div>
      );
    }
  }
}

export default Portfolio;

I would like the modal to display the corresponding project details. Thank you for your help.


Solution

  • You need to have only 1 modal and pass data dynamically on item click,

    <ul className="portfolio-list">
        {items.map(item => (
        <li key={item.client}>
            <div className="vignette">
                <button onClick={()=> this.handleShow(item)}> //Pass complete item object here
                    <h4>{item.client}</h4>
                    <div className="filtre"></div>
                    <img src={item.vignette} alt={item.titre} />
                </button>
            </div>
        </li>
        ))}
        <Modal show={this.state.show} onHide={this.handleClose}> //Only one modal
            <Modal.Header closeButton>
                <h3>{this.state.activeItem.client}</h3>
            </Modal.Header>
            <Modal.Body>
                <p>{this.state.activeItem.description}</p>
            </Modal.Body>
        </Modal>
    </ul>
    

    Now in your handleShow function you can set item to state,

    this.handleShow = (item) => {
       this.setState({activeItem:item}, ()=> this.setState({ show: true }));
    };
    

    Use callback to show modal, which makes sure activeItem have latest clicked item.

    Initial state,

    this.state = {
       error: null,
       isLoaded: false,
       items: [],
       projectName: "",
       show: false,
       activeItem:'' //new added
    };