React-modal: Multiple modal appear when clicked.

Created on 27 Oct 2017  路  7Comments  路  Source: reactjs/react-modal

Summary:

When the Modal component is inserted in a component that will be looped, say some money transactions sent from a server. When one of those components is clicked it creates the same number of Modal elements as the number of transactions.

Expected behavior:

It should only appear one Modal element in the DOM.

Link to example of issue:

https://codesandbox.io/s/7k9kolkmv0

Additional notes:

To replicate in the sandbox, please click on "grey" or "black" words in the table

Most helpful comment

Hi @luis-alves The state isActive, a boolean, will be shared for all the modals, so if one is toggled, all will be.

One option is to move the modal outside of the loop and the other one would be to change isActive to be a string or a number.

If you change the type to be a string, you can open each modal like this:

toggleModal = (id /* this value will be bound in the entries loop. */, event) => {
  let st = null;
  if (id > 0) {
    st = "modal" + String(id);
  }
  this.setState({ isActive: st });
}

const rows = this.props.entries.map((row) =>
        <div style={styleOfI} key={row._id} id={row._id}>
          <i 
            onClick={this.toggleModal.bind(row._id)}>
            <Modal isOpen={this.state.isActive == "modal" + String(row._id)}
              onRequestClose={this.toggleModal.bind(0)}
              ...

If moved outside of the loop:

setRow => (row, event) => this.setState({ row });

const rows = this.props.entries.map((row) =>
    ....
    <i onClick={this.setRow.bind(row)}>

return (
        <div>
          <div className="article-row">{rows}</div>
          <Modal isOpen={this.state.row != null} ... onRequestClose={this.setRow.bind(null)}>
             <h5 style={InsideOfI}>{this.state.row.date}</h5>
             <h5 style={InsideOfI}>{this.state.row.payee}</h5>
             <h5 style={InsideOfI}>{this.state.row.amount}</h5>
         </Modal>
        </div>
      )

All 7 comments

Hi @luis-alves The state isActive, a boolean, will be shared for all the modals, so if one is toggled, all will be.

One option is to move the modal outside of the loop and the other one would be to change isActive to be a string or a number.

If you change the type to be a string, you can open each modal like this:

toggleModal = (id /* this value will be bound in the entries loop. */, event) => {
  let st = null;
  if (id > 0) {
    st = "modal" + String(id);
  }
  this.setState({ isActive: st });
}

const rows = this.props.entries.map((row) =>
        <div style={styleOfI} key={row._id} id={row._id}>
          <i 
            onClick={this.toggleModal.bind(row._id)}>
            <Modal isOpen={this.state.isActive == "modal" + String(row._id)}
              onRequestClose={this.toggleModal.bind(0)}
              ...

If moved outside of the loop:

setRow => (row, event) => this.setState({ row });

const rows = this.props.entries.map((row) =>
    ....
    <i onClick={this.setRow.bind(row)}>

return (
        <div>
          <div className="article-row">{rows}</div>
          <Modal isOpen={this.state.row != null} ... onRequestClose={this.setRow.bind(null)}>
             <h5 style={InsideOfI}>{this.state.row.date}</h5>
             <h5 style={InsideOfI}>{this.state.row.payee}</h5>
             <h5 style={InsideOfI}>{this.state.row.amount}</h5>
         </Modal>
        </div>
      )

The second option is preferable because it won't create a modal per row.

Got it @diasbruno! But still can't close the modal by clicking the overlay.
Shouldn't it be <i onClick={this.setRow.bind(row, event)}> and setRow => (row, event) => this.setState({ row: event });? With these changes it works.

Oh sorry, it should be this.setRow.bind(null, row).

I prefer using setRow = row => event => { ... } and this.setRow(row) because it is a bit more clear.

Just as an example of how bind works (to remind myself).

var f = (x, y) => { console.log(x, y); };
f(1, 2);
1 2
var f2 = f.bind(null, 1 /* x */);
f2(2 /* y */);
1 2
var f3 = f.bind(null, 1 /* x */).bind(null, 2 /* y */); 
f3();
1 2

And for me to know ;)

Hi @luis-alves The state isActive, a boolean, will be shared for all the modals, so if one is toggled, all will be.

One option is to move the modal outside of the loop and the other one would be to change isActive to be a string or a number.

If you change the type to be a string, you can open each modal like this:

toggleModal = (id /* this value will be bound in the entries loop. */, event) => {
  let st = null;
  if (id > 0) {
    st = "modal" + String(id);
  }
  this.setState({ isActive: st });
}

const rows = this.props.entries.map((row) =>
        <div style={styleOfI} key={row._id} id={row._id}>
          <i 
            onClick={this.toggleModal.bind(row._id)}>
            <Modal isOpen={this.state.isActive == "modal" + String(row._id)}
              onRequestClose={this.toggleModal.bind(0)}
              ...

If moved outside of the loop:

setRow => (row, event) => this.setState({ row });

const rows = this.props.entries.map((row) =>
    ....
    <i onClick={this.setRow.bind(row)}>

return (
        <div>
          <div className="article-row">{rows}</div>
          <Modal isOpen={this.state.row != null} ... onRequestClose={this.setRow.bind(null)}>
             <h5 style={InsideOfI}>{this.state.row.date}</h5>
             <h5 style={InsideOfI}>{this.state.row.payee}</h5>
             <h5 style={InsideOfI}>{this.state.row.amount}</h5>
         </Modal>
        </div>
      )

Thank you!!

Was this page helpful?
0 / 5 - 0 ratings