Reputation: 1041
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>
);
}
}
Upvotes: 9
Views: 2608
Reputation: 3548
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
Upvotes: 1
Reputation: 897
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
Upvotes: 4
Reputation: 4526
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"));
Upvotes: 0
Reputation: 742
You need to call setState() of ProductItemModal to close Model. Otherwise, though isOpen is changed, the UI is not re-rendered.
Upvotes: 3