I am looking for a way to hide a div once the button thats in it is clicked and continue to show all other div's.
I've tried using the setState method, however when setting it to false with onClick() all of my objects disappear.
class App extends React.PureComponent {
state: {
notHidden: false,
}
constructor(props: any) {
super(props);
this.state = {search: '', notHidden: true};
this.hideObject = this.hideObject.bind(this)
}
hideThisDiv() {
this.setState({notHidden: false})
}
async componentDidMount() {
this.setState({
objects: await api.getObjects()
});
}
render = (objects: Object[]) => {
return ({Object.map((object) =>
<div key={index} className='class'>
<button className='hide' type='button' onClick={() => hideThisDiv()}>Hide</button>
<p>object.text</p>
</div>}
render() {
const {objects} = this.state;
return (<main>
<h1>Objects List</h1>
<header>
<input type="search" onChange={(e) => this.onSearch(e.target.value)}/>
</header>
{objects ? this.render(objects) : null}
</main>)
}
);
The data is a data.json file filled with many of these objects in the array
{
"uuid": "dsfgkj24-sfg34-1r134ef"
"text": "Some Text"
}
Edit: Sorry for the badly asked question, I am new to react.
Not tested, just a blueprint... is it what you want to do?
And yes I didn't hide, I removed but again, just an idea on how you can hide button separately, by keeping in state which ones are concerned.
function MagicList() {
const [hidden, hiddenSet] = useState([]);
const items = [{ id:1, text:'hello'}, { id:2, text:'from'}, { id:3, text:'the other sided'}]
const hideMe = id => hiddenSet([...hidden, id]);
return {
items.filter( item => {
return hidden.indexOf(item.id) !== -1;
})
.map( item => (
<button key={item.id} onClick={hideMe.bind(this, item.id)}>{item.text}</button>
))
};
}
Edition
const hideMe = id => hiddenSet([...hidden, id]);
It is just a fancy way to write:
function hideMe(id) {
const newArray = hidden.concat(id);
hiddenSet(newArray);
}
I suggest using a Set, Map, or object, to track the element ids you want hidden upon click of button. This provides O(1) lookups for what needs to be hidden. Be sure to render your actual text and not a string literal, i.e. <p>{object.text}</p> versus <p>object.text</p>.
class MyComponent extends React.PureComponent {
state = {
hidden: {}, // <-- store ids to hide
objects: [],
search: "",
};
// Curried function to take id and return click handler function
hideThisDiv = id => () => {
this.setState(prevState => ({
hidden: {
...prevState.hidden, // <-- copy existing hidden state
[id]: id // <-- add new id
}
}));
}
...
render() {
const { hidden, objects } = this.state;
return (
<main>
...
{objects
.filter((el) => !hidden[el.uuid]) // <-- check id if not hidden
.map(({ uuid, text }) => (
<div key={uuid}>
<button
type="button"
onClick={this.hideThisDiv(uuid)} // <-- attach handler
>
Hide
</button>
<p>{text}</p>
</div>
))}
</main>
);
}
}
Related
The remove() function is called from an object. How can I get updated state value inside of that remove() function.
const [InfoBoxPin, setInfoBoxPin] = useState([])
const createInfoBoxPin = (descriptions) =>{
var newPin = {
"location":currentLoc,
"addHandler":"mouseover",
"infoboxOption": {
title: 'Comment',
description: "No comment Added",
actions: [{
label:'Remove Pin',
eventHandler: function () {
remove(newPin.location) //FUNCTION CALLED HERE
}
}] }
}
setInfoBoxPin((InfoBoxPin)=>[...InfoBoxPin, newPin ]) // UPDATE STATE. Push the above object.
}
const remove = (pos) =>{
console.log(InfoBoxPin) //NEVER GETTING UPDATED STATE HERE.
//Other codes here......
}
This is a bing map Info card. Eventhandler creates a button which can call any function.
The problem is that you are referring to old state information in the remove function.
When you call setInfoBoxPin - the state of InfoBoxPin is registered for an update on the next render of UI. This means that in current state it will be the same (empty) and all links to it will refer to an empty array.
In order to fix this, you will have to pass your new state to appropriate functions from the View itself.
Example #1
Here, I have created a CodeSandBox for you:
https://codesandbox.io/s/react-setstate-example-4d5eg?file=/src/App.js
And here is the code snipped from it:
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [state, setState] = useState({
InfoBoxPin: [],
output: []
});
const createInfoBoxPin = (descriptions) => {
var newPin = {
location: Math.round(Math.random(10) * 1000),
addHandler: "mouseover",
infoboxOption: {
title: "Comment",
description: "No comment Added",
actions: [
{
label: "Remove Pin",
eventHandler: removePin
},
{
label: "Update Pin",
eventHandler: updatePin
}
]
}
};
setState({ ...state, InfoBoxPin: [...state.InfoBoxPin, newPin] });
};
const updatePin = (key, state) => {
var text = `Updating pin with key #${key} - ${state.InfoBoxPin[key].location}`;
setState({ ...state, output: [...state.output, text] });
console.log(text, state.InfoBoxPin);
};
const removePin = (key, state) => {
var text = `Removing pin with key #${key} - ${state.InfoBoxPin[key].location}`;
setState({ ...state, output: [...state.output, text] });
console.log(text, state.InfoBoxPin);
};
return (
<div className="App">
<h1>React setState Example</h1>
<h2>Click on a button to add new Pin</h2>
<button onClick={createInfoBoxPin}>Add new Pin</button>
<div>----</div>
{state.InfoBoxPin.map((pin, pin_key) => {
return (
<div key={pin_key}>
<span>Pin: {pin.location} </span>
{pin.infoboxOption.actions.map((action, action_key) => {
return (
<button
key={action_key}
onClick={() => action.eventHandler(pin_key, state)}
>
{action.label}
</button>
);
})}
</div>
);
})}
<h4> OUTPUT </h4>
<ul style={{ textAlign: "left" }}>
{state.output.map((txt, i) => {
return <li key={i}>{txt}</li>;
})}
</ul>
</div>
);
}
As you can see I am providing a new state with InfoBoxPin value to a function named eventHandler for onclick event listener of a button.
And then in that function, I can use the new InfoBoxPin value from state how I need it.
Example #2 (ES6)
In this example, I am using a bit different structure for App - using class (ES6)
By using a class for App, we can manipulate App state using different methods.
func.bind(this) can be used on defined function on initialization
func.call(this) can be used to call a dynamic function without arguments
func.apply(this, [args]) can be used to call a dynamic function with arguments
CodeSandBox Link:
https://codesandbox.io/s/react-setstate-example-using-class-cz2u4?file=/src/App.js
Code Snippet:
import React from "react";
import "./styles.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
InfoBoxPin: [],
pinName: ""
};
/* ------------ method #1 using .bind(this) ------------ */
this.setPinName = this.setPinName.bind(this);
}
remove(key) {
this.state.InfoBoxPin.splice(key, 1);
this.setState({ InfoBoxPin: this.state.InfoBoxPin });
}
add(pinName) {
this.state.InfoBoxPin.push(pinName);
this.setState({ InfoBoxPin: this.state.InfoBoxPin });
}
processPinNameAndAdd() {
let pinName = this.state.pinName.trim();
if (pinName === "") pinName = Math.round(Math.random() * 1000);
this.add(pinName);
}
setPinName(event) {
this.setState({ pinName: event.target.value });
}
render() {
return (
<div className="shopping-list">
<h1>Pin List</h1>
<p>Hit "Add New Pin" button.</p>
<p>(Optional) Provide your own name for the pin</p>
<input
onInput={this.setPinName}
value={this.state.pinName}
placeholder="Custom name"
></input>
{/* ------------ method #2 using .call(this) ------------ */}
<button onClick={() => this.processPinNameAndAdd.call(this)}>
Add new Pin
</button>
<ul>
{this.state.InfoBoxPin.map((pin, pinKey) => {
return (
<li key={pinKey}>
<div>pin: {pin}</div>
{/* ------------ method #3 using .apply(this, [args]) ------------ */}
<button onClick={() => this.remove.apply(this, [pinKey])}>
Delete Pin
</button>
</li>
);
})}
</ul>
</div>
);
}
}
export default App;
Example #3 (ES6) without access to the created element
This example will show how to handle callbacks from third-party libraries with our own arguments and state data from the event of an auto-generated HTML element
CodeSandBox Link:
https://codesandbox.io/s/react-setstate-example-using-class-no-element-control-lcz5d?file=/src/App.js
Code Snippet:
import React from "react";
import "./styles.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
InfoBoxPin: [],
lastPinId: 0,
pinName: ""
};
this.setPinName = this.setPinName.bind(this);
}
remove(id) {
let keyToRemove = null;
this.state.InfoBoxPin.forEach((pin, key) => {
if (pin.id === id) keyToRemove = key;
});
this.state.InfoBoxPin.splice(keyToRemove, 1);
this.setState({ InfoBoxPin: this.state.InfoBoxPin });
}
add(data, id) {
this.state.InfoBoxPin.push({ id: id, data: data });
this.setState({
InfoBoxPin: this.state.InfoBoxPin,
lastPinId: id
});
}
processPinNameAndAdd() {
let pinName = this.state.pinName.trim();
if (pinName === "") pinName = Math.round(Math.random() * 1000);
var newPinId = this.state.lastPinId + 1;
var newPin = {
location: pinName,
addHandler: "mouseover",
infoboxOption: {
title: "Comment",
description: "No comment Added",
actions: [
{
label: "Remove Pin #" + newPinId,
// [ES6 class only] using () => func() for callback function
// By doing so we don't need to use bind,call,apply to pass class ref [this] to a function.
eventHandler: () => this.remove(newPinId)
}
]
}
};
this.add(newPin, newPinId);
}
setPinName(event) {
this.setState({ pinName: event.target.value });
}
render() {
return (
<div className="shopping-list">
<h1>Pin List</h1>
<p>Hit "Add New Pin" button.</p>
<p>(Optional) Provide your own name for the pin</p>
<input onInput={this.setPinName} value={this.state.pinName}></input>
{/*
[ES6 class only] Using {() => func()} for event handler.
By doing so we don't need to use func.bind(this) for passing class ref at constructor
*/}
<button onClick={() => this.processPinNameAndAdd()}>Add new Pin</button>
<ul>
{this.state.InfoBoxPin.map((pin, pKey) => {
return (
<li key={pKey}>
<div>pin: {pin.data.location}</div>
{pin.data.infoboxOption.actions.map((action, aKey) => {
return (
<button key={aKey} onClick={action.eventHandler}>
{action.label}
</button>
);
})}
</li>
);
})}
</ul>
</div>
);
}
}
export default App;
I have created lastPinId entry in a State to track newly created Pin's ids.
Pin id can be used later to find the desired pin in the InfoBoxPin collection for removal.
The most important part how to register your eventHandler is this:
eventHandler: () => this.remove(newPinId)
Please note that using arrow function () => func is important to pass class ref to remove function.
I'm trying to modify state and take the new state to render.
When I click and modified(added {isClicked: true} to array), console.log(this.state.listOfQuotes) inside onClicked function returns modified the full array of state(which I want to use)
but after render, console.log(this.state.listOfQuotes) returns only one clicked element and not even modified one...
Any help/hint much appreciated!
Here is my code
import React from "react";
export class Quotes extends React.Component {
constructor(props) {
super(props);
this.state = { listOfQuotes: [] };
this.vote = this.vote.bind(this);
this.onClicked = this.onClicked.bind(this);
}
componentDidMount() {
const url = "https://programming-quotes-api.herokuapp.com/quotes";
fetch(url)
.then(res => res.json())
.then(quote => {
this.setState({
listOfQuotes: quote
});
});
}
onClicked(id) {
const quotes = [...this.state.listOfQuotes];
const clickedQuote = quotes.findIndex(quote => quote.id === id);
console.log("onclicked", clickedQuote);
const newArray = { ...quotes[clickedQuote], isClicked: true };
console.log(newArray);
this.setState(prevState => ({
listOfQuotes: [
...prevState.listOfQuotes.splice(clickedQuote, 1, newArray)
]
}));
console.log(this.state.listOfQuotes); ----------> this one returns what i want
}
render() {
console.log(this.state.listOfQuotes); -----------> i want to have same result as above state
return (
<div className="quotes">
<div>
{this.state.listOfQuotes.map((quote, idx) => (
<div key={idx}>
<div onClick={() => this.onClicked(quote.id)}>
{!quote.isClicked ? (
<div className="before-clicked">{quote.en}</div>
) : (
<div className="after-clicked">{quote.en}</div>
)}
</div>
<div>By {quote.author}</div>
<div>Rating {quote.rating}</div>
<div className="vote">
<span>{quote.numberOfVotes}</span>
</div>
</div>
))}
</div>
</div>
);
}
}
There is a problem with your onClicked method.
It is not modifying the array correctly.
In my opinion, this is how it could have done.
onClicked(id) {
let quotes = [...this.state.listOfQuotes];
const clickedQuoteIndex = quotes.findIndex(quote => quote.id === id);
// Modify the object on the found index and assign true to "isClicked"
quotes[clickedQuoteIndex].isClicked = true;
// And then setState with the modified array
// Since setState is async, so the console shouldn't be called immediately
// but rather in the callback
this.setState({ listOfQuotes: quotes }, () => {
console.log(this.state.listOfQuotes);
});
}
I have a .map() function where I'm iterating over an array and rendering elements, like so:
{options.map((option, i) => (
<TachyonsSimpleSelectOption
options={options[i]}
key={i}
onClick={() => this.isSelected(i)}
selected={this.toggleStyles("item")}
/>
I am toggling the state of a selected element like so:
isSelected (i) {
this.setState({ selected: !this.state.selected }, () => { console.log(this.state.selected) })
}
Using a switch statement to change the styles:
toggleStyles(el) {
switch (el) {
case "item":
return this.state.selected ? "bg-light-gray" : "";
break;
}
}
And then passing it in my toggleStyles method as props to the className of the TachyonsSimpleSelectOption Component.
Problem
The class is being toggled for all items in the array, but I only want to target the currently clicked item.
Link to Sandbox.
What am I doing wrong here?
You're using the selected state incorrectly.
In your code, to determine whether it is selected or not, you depends on that state, but you didn't specify which items that is currently selected.
Instead saving a boolean state, you can store which index is currently selected so that only specified item is affected.
This may be a rough answer, but I hope I can give you some ideas.
on your render:
{options.map((option, i) => (
<TachyonsSimpleSelectOption
options={options[i]}
key={i}
onClick={() => this.setState({ selectedItem: i })}
selected={this.determineItemStyle(i)}
/>
))}
on the function that will determine the selected props value:
determineItemStyle(i) {
const isItemSelected = this.state.selectedItem === i;
return isItemSelected ? "bg-light-gray" : "";
}
Hope this answer will give you some eureka moment
You are not telling react which element is toggled. Since the state has just a boolean value selected, it doesn't know which element is selected.
In order to do that, change your isSelected function to :
isSelected (i) {
this.setState({ selected: i }, () => {
console.log(this.state.selected) })
}
Now, the React state knows that the item on index i is selected. Use that to toggle your class now.
In case you want to store multiple selected items, you need to store an array of indices instead of just one index
TachyonsSimpleSelectOption.js:
import React from 'react';
class Option extends React.Component {
render() {
const { selected, name } = this.props;
return(
<h1
onClick={() => this.props.onClick()}
style={{backgroundColor: selected ? 'grey' : 'white'}}
>Hello {name}!</h1>
)
}
}
export default Option;
index.js:
import React from "react";
import { render } from "react-dom";
import TachyonsSimpleSelectOption from "./TachyonsSimpleSelectOption";
const options = ["apple", "pear", "orange"];
const styles = {
selected: "bg-light-gray"
};
class Select extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
selected: []
};
this.handleClick = this.handleClick.bind(this);
this.handleBlur = this.handleBlur.bind(this);
this.isSelected = this.isSelected.bind(this);
}
handleBlur() {
this.toggleMenu(close);
}
handleClick(e) {
this.toggleMenu();
}
toggleMenu(close) {
this.setState(
{
open: !this.state.open
},
() => {
this.toggleStyles("menu");
}
);
}
toggleStyles(el, index) {
switch (el) {
case "menu":
return this.state.open ? "db" : "dn";
break;
case "item":
const { selected } = this.state;
return selected.indexOf(index) !== -1;
break;
}
}
isSelected(i) {
let { selected } = this.state;
if (selected.indexOf(i) === -1) {
selected.push(i);
} else {
selected = selected.filter(index => index !== i);
}
this.setState({ selected});
}
render() {
const { options } = this.props;
return (
<div
className="flex flex-column ba"
onBlur={this.handleBlur}
tabIndex={0}
>
<div className="flex-row pa3" onClick={this.handleClick}>
<span className="flex-grow-1 w-50 dib">Title</span>
<span className="flex-grow-1 w-50 dib tr">^</span>
</div>
<div className={this.toggleStyles("menu")}>
{options.map((option, i) => (
<TachyonsSimpleSelectOption
name={options[i]}
key={i}
onClick={() => this.isSelected(i)}
selected={this.toggleStyles("item", i)}
/>
))}
</div>
</div>
);
}
}
render(<Select options={options} />, document.getElementById("root"));
And Link to Sandbox.
I have a button for each div. And when I press on it, it has to show the div with the same key, and hide the others.
What is the best way to do it ? This is my code
class Main extends Component {
constructor(props) {
super(props);
this.state = {
messages: [
{ message: "message1", key: "1" },
{ message: "message2", key: "2" }
]
};
}
handleClick(message) {
//something to show the specific component and hide the others
}
render() {
let messageNodes = this.state.messages.map(message => {
return (
<Button key={message.key} onClick={e => this.handleClick(message)}>
{message.message}
</Button>
)
});
let messageNodes2 = this.state.messages.map(message => {
return <div key={message.key}>
<p>{message.message}</p>
</div>
});
return <div>
<div>{messageNodes}</div>
<div>{messageNodes2}</div>
</div>
}
}
import React from "react";
import { render } from "react-dom";
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
messages: [
{ message: "message1", id: "1" },
{ message: "message2", id: "2" }
],
openedMessage: false
};
}
handleClick(id) {
const currentmessage = this.state.messages.filter(item => item.id === id);
this.setState({ openedMessage: currentmessage });
}
render() {
let messageNodes = this.state.messages.map(message => {
return (
<button key={message.id} onClick={e => this.handleClick(message.id)}>
{message.message}
</button>
);
});
let messageNodes2 = this.state.messages.map(message => {
return (
<div key={message.key}>
<p>{message.message}</p>
</div>
);
});
const { openedMessage } = this.state;
console.log(openedMessage);
return (
<div>
{openedMessage ? (
<div>
{openedMessage.map(item => (
<div>
{" "}
{item.id} {item.message}{" "}
</div>
))}
</div>
) : (
<div> Not Opened</div>
)}
{!openedMessage && messageNodes}
</div>
);
}
}
render(<Main />, document.getElementById("root"));
The main concept here is this following line of code.
handleClick(id) {
const currentmessage = this.state.messages.filter(item => item.id === id);
this.setState({ openedMessage: currentmessage });
}`
When we map our messageNodes we pass down the messages id. When a message is clicked the id of that message is passed to the handleClick and we filter all the messages that do not contain the id of the clicked message. Then if there is an openedMessage in state we render the message, but at the same time we stop rendering the message nodes, with this logic {!openedMessage && messageNodes}
Something like this. You should keep in state only message key of visible component and in render method you should render only visible component based on the key preserved in state. Since you have array of message objects in state, use it to render only button that matches the key.
class Main extends Component {
constructor(props) {
super(props);
this.state = {
//My array messages: [],
visibleComponentKey: '',
showAll: true
};
handleClick(message) {
//something to show the specific component and hide the others
// preserve in state visible component
this.setState({visibleComponentKey : message.key, showAll: false});
};
render() {
const {visibleComponentKey, showAll} = this.state;
return (
<div>
{!! visibleComponentKey && ! showAll &&
this.state.messages.filter(message => {
return message.key == visibleComponentKey ? <Button onClick={e => this.handleClick(message)}>{message.message}</Button>
) : <div /> })
}
{ !! showAll &&
this.state.messages.map(message => <Button key={message.key} onClick={e => this.handleClick(message)}>{message.message}</Button>)
}
</div>
);
}
}
I haven't tried it but it gives you a basic idea.
I cannot reply to #Omar directly but let me tell you, this is the best code explanation for what i was looking for! Thank you!
Also, to close, I added a handleClose function that set the state back to false. Worked like a charm!
onCloseItem =(event) => {
event.preventDefault();
this.setState({
openedItem: false
});
}
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>
);
}
}
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
You need to call setState() of ProductItemModal to close Model. Otherwise, though isOpen is changed, the UI is not re-rendered.
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
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