9
votes

I'm using this react modal plugin: https://github.com/reactjs/react-modal and I need to show an array of objects in the modal on page load. When the first item shows user clicks a button so isOpen prop is set to false for Modal. Each item has a prop showModal that feeds the value to isOpen for the Modal. As the user keeps clicking I keep setting the value on the current object to false and then set it true for the next object. This is all working fine but the problem is that the overlay and dialog window stays on screen and only content within the modal is updated. I would like the modal to fully close and open to show content of the next object in array. I had to strip out my code to a simplified version below:

class ProductsModal extends React.Component {
  constructor(props) {
    super(props);
    this.remindMeHandler = this.remindMeHandler.bind(this);

    this.state = {
      products: [],
      modalId: 0
    };
  }

showModals() {
    let products = this.state.products;
    //A different function saves the array of product objects in the state so 
    //I can show them one by one

    let currentProduct = products[this.state.popUpId];

    if (products.length > 0) {
      return <ProductItemModal 
              product={currentProduct}
              showNextPopUp={() => this.showNextPopUp(currentProduct.productId)}
              showPopUp={currentProduct['showModal']}
              />;
    //showModal is a boolean for each product that sets the value of isOpen
    }
  }

  showNextPopUp() {
      //All this does is sets the "showModal" property to false for current 
     //product and sets it to true for next product so it shows in the Modal
  }


render() {
    return(
      <div>
        {this.showModals()}
      </div>
    );
  }
}

class ProductItemModal extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return(
      <Modal 
        isOpen={this.props.showModal}
        contentLabel="Product"
        id={this.props.product.productId}
        >
        <div>
         Product Data......
        </div>
      </Modal>
    );
  }
}
4
Your question is unclear. Can you explain what you are trying to accomplish better?Alexander Higgins
@AlexanderHiggins Ok so you know how the modal opens and there is an ovelay? I want this to close everytime isOpen is set to false as I'm going thru the products. Right now it does not happen, Modal with the overlay stays open as I'm going through products. Does it makes sense?sayayin
Your modal's isOpen has a prop of showModal but in your ProductItemModal I don't see you setting showModal at all. Did you mean for showPopUp to be showModal?Simon
@Simon that is coming from this.props.showModal, in the parent component showPopUp={currentProduct['showModal']} is what is being sent from the parent. All these changes in the state are handled in showNextPopUp()sayayin
I have troubleshooted the value for isOpen, everything is good there. There is something else I need to do here I guess. Modal opens fine and when the last product's showModal property is set to false, it closes. But in between it stays open and just refreshes the product content.sayayin

4 Answers

4
votes

Had a workaround for all your problems and created this codepen link. It would be like this,

class ProductItemModal extends React.Component {
  render() {
    const { showModal, product, showNextModal, onClose } = this.props;

    return(
      <ReactModal 
        isOpen={showModal}
        contentLabel="Product"
        onRequestClose={() => onClose()}
        >
        <p>
          <b>Product Id</b> - {product.id}, <b>Product Name</b> - {product.name}
        </p>
        <button onClick={() => showNextModal()}>Next</button>
      </ReactModal>
    );
  }
}

class ProductsModal extends React.Component {
  constructor() {
    super();

    this.state = {
      products: [
        {id: 1, name: "Mac", showModal: true},
        {id: 2, name: "iPhone", showModal: false},
        {id: 3, name: "iPod", showModal: false},
      ],
      modalId: 0
    };
  }

  handleProductItemModalClose(product) {
    //backdrop click or escape click handling here
    console.log(`Modal closing from Product - ${product.name}`);
  }

  showModals() {
    const { products, modalId } = this.state;
    //A different function saves the array of product objects in the state so 
    //I can show them one by one

    let currentProduct = products[modalId];

    if(currentProduct) {
      return <ProductItemModal 
              product={currentProduct}
              showNextModal={() => this.showNextModal(currentProduct.id)}
              showModal={currentProduct["showModal"]}
              onClose={() => this.handleProductItemModalClose(currentProduct)}
              />;
    //showModal is a boolean for each product that sets the value of isOpen
    }
  }

  showNextModal(currentProductId) {
    const { products, modalId } = this.state;

    var isLastModal = false;
    if(modalId === products.length - 1) {
      isLastModal = true;
    }

    var clonedProducts = [...products];
    var currentIndex = clonedProducts.findIndex(product => product.id === currentProductId);
    var newIndex = currentIndex + 1;
    clonedProducts[currentIndex].showModal = false;
    if(!isLastModal) {
      clonedProducts[newIndex].showModal = true;
    } else {
      //comment the following lines if you don't wanna show modal again from the start
      newIndex = 0;
      clonedProducts[0].showModal = true;
    }
      //All this does is sets the "showModal" property to false for current 
     //product and sets it to true for next product so it shows in the Modal
    this.setState({
      products: clonedProducts
    }, () => {
      this.setState({
        modalId: newIndex
      });
    });
  }

  render() {
    return(
      <div>
        {this.showModals()}
      </div>
    );
  }
}

ReactDOM.render(<ProductsModal />, document.getElementById("main"));

Let me know if it helps.

Updated codepen: https://codepen.io/anon/pen/rzVQrw?editors=0110

3
votes

You need to call setState() of ProductItemModal to close Model. Otherwise, though isOpen is changed, the UI is not re-rendered.

1
votes

As you probably know that react maintain a virtual DOM and on every time state or props change it compares the difference between browser's DOM(actual dom) and virtual DOM(the one that React maintain) and in your code every time you change the isOpen property all you are doing is only changing the props of the Model component that's why React only update the internal content of the actual Model

to completely close and re-open the model you need to do a small change in your code

instead of returning only one model component in your ProductsModal you need to do something like this so that react know that this modal has been close and otherone has been open Key property is important for performance reason read more

class ProductsModal extends React.Component {
 .
 .
 .

showModals() {
    let products = this.state.products;
    //A different function saves the array of product objects in the state so 


    if (products.length > 0) {
      return (
        //return list of all modal component
        products.map((product) =>
           <ProductItemModal 
              product={product}
              showNextPopUp={() => this.showNextPopUp(product.productId)}
              showPopUp={product['showModal']}
              key={product.productId}
              />
        )
      );
    //showModal is a boolean for each product that sets the value of isOpen
    }
  }

 .
 . 
 .
}

all you are doing here is just returning multiple modal and when one model gets the isOpen props as false is close and the other witch gets true is open and now react know that there are two different modals because of key props

0
votes

Another work around is to use setTimeout. Implementation is as follows-

class ProductItemModal extends React.Component {
  render() {
    const { showModal, product, selectNextProductFunc, onClose } = this.props;

    return(
      <ReactModal 
        isOpen={showModal}
        contentLabel="Product"
        onRequestClose={() => onClose()}
        >
        <p>
          <b>Product Id</b> - {product.id}, <b>Product Name</b> - {product.name}
        </p>
        <button onClick={() => selectNextProductFunc(product)}>Next</button>
      </ReactModal>
    );
  }
}

class ProductsModal extends React.Component {
  constructor() {
    super();

    this.state = {
      products: [
        {id: 1, name: "Mac"},
        {id: 2, name: "iPhone"},
        {id: 3, name: "iPod"},
      ],
      productId: null,
      showModal: true,
    };
  }

  handleProductItemModalClose(product) {
    //backdrop click or escape click handling here
    console.log(`Modal closing from Product - ${product.name}`);
  }


  showModals() {
    const { products, productId, showModal} = this.state;
    //A different function saves the array of product objects in the state so 
    //I can show them one by one
    const getProduct = function(){
      if(productId){
        return products.find((i) => i.id === productId);
      }else{
        return products[0]; // first element
      }
    }


      return <ProductItemModal 
              product={getProduct()}
              selectNextProductFunc={this.selectNextProductFunc.bind(this)}
              showModal={showModal}
              onClose={() => this.handleProductItemModalClose()}
              />;
    //showModal is a boolean for each product that sets the value of isOpen

  }

  selectNextProductFunc(currentProduct) {
    const { products} = this.state;
    this.setState({
      showModal: false
    });

    const currentProductIndex = products.findIndex((i) => i.id === currentProduct.id);
    const modifiedIndex = 0;
    if(products[currentProductIndex + 1]){
      this.setState({
         productId : products[currentProductIndex + 1].id,
      });
    }else{
      this.setState({
         productId : modifiedIndex,
      });
    }

    setTimeout(() => {
        this.setState({
          showModal: true
        })
    }, 1000);

  }

  render() {
    return(
      <div>
        {this.showModals()}
      </div>
    );
  }
}

ReactDOM.render(<ProductsModal />, document.getElementById("main"));

jsbin