Search code examples
javascriptreactjsbootstrap-4bootstrap-modalreact-bootstrap

React Modal always uses same record from a row


so I've got a table displaying multiple jobs from an API call. Each table row has a button that opens a bootstrap modal displaying further information about said job.

My problem is that the modal doesn't seem to be opening for the row clicked. It always opens a specific row (the one with the lowest id). Anyone know what I'm doing wrong? ModalComponent

render() {
  console.log("job: ", this.props.id)
  return (
    <div className="container">
      <ReactBootstrap.Modal
        {...this.props}
        size="lg"
        aria-labelledby="contained-modal-title-vcenter"
        centered
      >
        <ReactBootstrap.Modal.Header closeButton>
          <ReactBootstrap.Modal.Title id="contained-modal-title-vcenter">
            {this.props.title}
            {this.props.employer}
          </ReactBootstrap.Modal.Title>
        </ReactBootstrap.Modal.Header>
        <ReactBootstrap.Modal.Body>
          <h4>Would you like to apply to this job?</h4>
          <p>{this.props.description}</p>
        </ReactBootstrap.Modal.Body>
        <ReactBootstrap.Modal.Footer>
          <button className="btn btn-primary" onClick={this.applyJob}>Apply</button>
          <button className="btn btn-warning" onClick={this.props.onHide}>Close</button>
        </ReactBootstrap.Modal.Footer>
      </ReactBootstrap.Modal>
    </div>
  )
}

The console.log spits out the id of every row in the table.

My TableComponent passes the job details of each job into the ModalComponent like so: TableComponent

<tbody>
  {
    this.props.response.map(    //  map allows you to loop around items
      job =>  //  a key is used to identify a row
        <tr key={job.id}>
          <td>{job.employer}</td>
          <td>{job.jobTitle}</td>
          <td>{job.county}</td>
          {/* <td>{job.applied + ''}</td> */}
          <td>
            {this.checkApplied(job.applied)}
          </td>
          <td><button className="btn btn-info" onClick={() => this.setState({ addModalShow: true })}>Info</button></td>
          <ApplyComponent
            show={this.state.addModalShow}
            onHide={addModalClose}
            id={job.id}
            title={job.jobTitle}
            employer={job.employer}
            county={job.county}
            description={job.description}
            status={job.applied}
          />
        </tr>
    )
  }
</tbody>

Here are images of the table and the modal: enter image description here

enter image description here

As you can see, the details form the bottom row is being displayed in the modal even when I click the top row.


Solution

  • The Problem

    You are mapping over several items that can each have their own modal. Each of those modals determines whether it is open or closed by the show prop.

    The show prop is set to this.state.addModalShow for every mapped element.

    The Result

    Every modal will open and close at the same time since they are based on the same state value. Clicking any item will cause all to open or close.

    The visible result is that you will always see the last modal as the open modal, since it was the last to render and will be on top.

    The Solution

    Here is one solution that would require minimal changes:

    Use a different value in state for each item.

    this.props.response.map(
      job =>
        <tr key={job.id}>
          <td>{job.employer}</td>
          <td>{job.jobTitle}</td>
          <td>{job.county}</td>
          <td>
            {this.checkApplied(job.applied)}
          </td>
          <td>
          <button 
            className="btn btn-info" 
            onClick={() => {
              // Use a computed property name to make it unique based on the ID
              this.setState({ ['show_'+job.id]: true })
            }
          >Info</button></td>
          <ApplyComponent
            show={this.state['show_'+job.id]} // Use the same state value as the prop
            onHide={() => {
              // Follow the same process for closing
              this.setState({ ['show_'+job.id]: false})
            }}
            id={job.id}
            title={job.jobTitle}
            employer={job.employer}
            county={job.county}
            description={job.description}
            status={job.applied}
          />
        </tr>
    )