I am using bootstrap modal and on click event on an element its opened but cant closed it when i click the "x" on the right corner of the modal.
The problem is that i do succeed to pass the state by props from parent to child but when i invoke the function "lgClose" inside the child, its goes to the parent component but doesnt actually changing the state position to "false".
I can see its go inside the function because i put "console.log('im in the function')" and i can see it. Why the state doesnt changed to false in parent component ?
import React, { Component } from 'react';
import { Link } from 'react-router-dom'
import { connect } from 'react-redux';
import { getAllUsers,getMessages } from './../actions';
import { bindActionCreators} from 'redux';
import { Button, Modal } from 'react-bootstrap';
import ContainerModal from './../components/popup_modal';
// parent component
class MainContainer extends Component {
constructor(props, context) {
super(props, context);
this.state = {
lgShow: false
};
}
componentWillMount(){
this.props.getAllUsers();
}
renderList = ({list}) => {
if(list){
return list.map((item)=>{
return(
<div key={item._id} className="item-list" onClick={() => this.setState({lgShow: true})}>
<div className="title">{item.firstName} {item.lastName}</div>
<div className="body">{item.age}</div>
<div>
<ContainerModal lgShow={this.state.lgShow} lgClose={this.lgClose}/>
</div>
</div>
)
})
}
}
lgClose = () => {
this.setState({lgShow:false});
console.log('im in the function');
}
render() {
console.log(this.state);
return (
<div className="App">
<div className="top">
<h3>Messages</h3>
<Link to="/form">Add</Link>
</div>
<div className="messages_container">
{this.renderList(this.props.users)}
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
messages:state.messages,
users:state.users
}
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({getAllUsers,getMessages},dispatch);
}
export default connect(mapStateToProps,mapDispatchToProps)(MainContainer);
import React from 'react';
import { Button, Modal } from 'react-bootstrap';
// child component
const ContainerModal = (props) => {
return (
<div>
<Modal
size="lg"
show={props.lgShow}
onHide={ props.lgClose }
aria-labelledby="example-modal-sizes-title-lg"
>
<Modal.Header closeButton>
<Modal.Title id="example-modal-sizes-title-lg">
Large Modal
</Modal.Title>
</Modal.Header>
<Modal.Body>item</Modal.Body>
</Modal>
</div>
)
}
export default ContainerModal;
Seems that this in the lgClose function is not pointing to MainContainer.
If that is the case, one way of solving it is to bind the lgClosefunction to MainContainer in the constructor:
constructor(props, context) {
super(props, context);
this.state = {
lgShow: false
};
this.lgClose = this.lgClose.bind(this); // Add this line
}
Hope this helps :)
In your ContainerModal try this:
import React from 'react';
import { Button, Modal } from 'react-bootstrap';
// child component
const ContainerModal = (props) => {
return (
<div>
<Modal
{...props}
size="lg"
aria-labelledby="example-modal-sizes-title-lg"
>
<Modal.Header closeButton>
<Modal.Title id="example-modal-sizes-title-lg">
Large Modal
</Modal.Title>
</Modal.Header>
<Modal.Body>item</Modal.Body>
</Modal>
</div>
)
}
export default ContainerModal;
And in renderList, do this:
<ContainerModal show={this.state.lgShow} onHide={this.lgClose} />
Here is the reason why your lgShow remains true.
This explanation would be little longer. So please bear with me.
Here is what you have done.
You have added your ContainerModal as a child of div (className="item-list") inside renderList method which has an onClick listener attached to itself.
When you click on the list item it works fine and the modal appears but when you click on the close button on the modal things become fishy.
This is because of the onClick listener of the div (className="item-list") getting called after the
ContainerModal's click listener is called setting the lgShow to true again. This behavior is termed as event bubbling where both parent and child receive event in a bottom-up way (i.e child receiving the first event followed by the parent).
To verify this behavior add a console.log to the onClick listener of the div.
Here is a link to a github question for the explanation on event bubbling
Solution
First and foremost suggestion would be to take out the ContainerModal from the renderList method to the root div. This is because right now for every list item of yours a ContainerModal is being created which is unnecessary. For example if you have 10 users in the list you are creating 10 ContainerModal (To verify this increase the number of users and you could see the shade of the modal getting darker.)
The solution to the above problem is to take the ContainerModal to the root level where it's onClick will not coincide with the onClick of the div (className="item-list") in the renderList and your problem would be solved.
Here is the modified solution.
import React, { Component } from 'react';
import { Link } from 'react-router-dom'
import { connect } from 'react-redux';
import { getAllUsers,getMessages } from './../actions';
import { bindActionCreators} from 'redux';
import { Button, Modal } from 'react-bootstrap';
import ContainerModal from './../components/popup_modal';
// parent component
class MainContainer extends Component {
constructor(props, context) {
super(props, context);
this.state = {
lgShow: false
};
}
componentWillMount(){
this.props.getAllUsers();
}
renderList = ({list}) => {
if(list){
return list.map((item)=>{
return(
<div key={item._id} className="item-list" onClick={() => this.setState({lgShow: true})}>
<div className="title">{item.firstName} {item.lastName}</div>
<div className="body">{item.age}</div>
<div>
{/* <ContainerModal lgShow={this.state.lgShow} lgClose={this.lgClose}/> */}
</div>
</div>
)
})
}
}
lgClose = () => {
this.setState({lgShow:false});
console.log('im in the function');
}
render() {
console.log(this.state);
return (
<div className="App">
<div className="top">
<h3>Messages</h3>
<Link to="/form">Add</Link>
</div>
<div className="messages_container">
{this.renderList(this.props.users)}
</div>
<ContainerModal lgShow={this.state.lgShow} lgClose={this.lgClose}/>
</div>
);
}
}
function mapStateToProps(state) {
return {
messages:state.messages,
users:state.users
}
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({getAllUsers,getMessages},dispatch);
}
export default connect(mapStateToProps,mapDispatchToProps)(MainContainer);
import React from 'react';
import { Button, Modal } from 'react-bootstrap';
// child component
const ContainerModal = (props) => {
return (
<div>
<Modal
size="lg"
show={props.lgShow}
onHide={ props.lgClose }
aria-labelledby="example-modal-sizes-title-lg"
>
<Modal.Header closeButton>
<Modal.Title id="example-modal-sizes-title-lg">
Large Modal
</Modal.Title>
</Modal.Header>
<Modal.Body>item</Modal.Body>
</Modal>
</div>
)
}
export default ContainerModal;
Look out for the changed position of Container Modal. Rest of the code is fine.
I have also uploaded a working solution to code sandbox. Have a look at it.
Hope this helps.
Related
{import React, { Component } from "react";
import { Modal } from 'reactstrap';
class ContactUsSuccessPopup extends Component {
constructor(props) {
super(props);
this.state = {
modal: true
};
}
toggle = () => {
this.props.hideModal(!this.state.modal)
}
render() {
return (
<div>
<Modal isOpen={this.state.modal}
toggle={this.toggle}
className="welcome_popup_container"
>
<div className="welcome_popup">
<div className="welcome_popup_main">
<div className="popup_right">
<h2>Welcome</h2>
</div>
</div>
</div>
</Modal>
</div >
);
}
}
export default ContactUsSuccessPopup;
I am using reactstarp modal, how can i make the modal close when i click outside? right now it close only if i click the dismiss button and up and bottom of modal. If I click left and right of the modal the modal doesn't close
Version "react": "^16.8.1" and "reactstrap": "^6.4.0"
Update the toggle method to properly change your state:
toggle() {
this.setState({modal: !this.state.modal})
}
I have a problem with update props.
I have two components.
The first is the App, where there is a button that opens or closes the modal.
The second is the modal structure.
The first component stores the state, I click the buton, the state changes, I send props to ModalComponent component (the modal is opening).
And in this component should change this state if I want to close modal.
But if I click on the Cancel button in modal, nothing happens.
The state does not change.
How to change it, how it would make communication between the child and the parent?
This is my code :
ModalComponent
import React, { Component } from "react";
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from "reactstrap";
class ModalComponent extends Component {
constructor(props) {
super(props);
this.state = {
modal: props.modal
};
this.toggle = this.toggle.bind(this);
}
toggle() {
const { modal } = this.state;
this.setState({
modal: !modal
});
}
render() {
const { modal } = this.props;
return (
<div>
<Modal isOpen={modal} toggle={this.toggle}>
<ModalHeader toggle={this.toggle}>Halo</ModalHeader>
<ModalFooter>
<Button onClick={this.toggle}>Cancel</Button>
</ModalFooter>
</Modal>
</div>
);
}
}
export default ModalComponent;
App
import React from "react";
import ReactDOM from "react-dom";
import Modal from "./modal";
import { Button } from "reactstrap";
import "./styles.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
modal: false
};
this.toggle = this.toggle.bind(this);
}
toggle() {
const { modal } = this.state;
this.setState({
modal: !modal
});
}
render() {
const { modal } = this.state;
return (
<div>
<Button
type="link"
onClick={this.toggle}
>
Delete account
</Button>
<Modal modal={modal} />
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
The state in your Modal Component would change but you aren't using it to set the isOpen props on Modal component.
Also you must not use state which is directly derivable from props. You must instead pass a handler from Parent to update the state in the parent itself
ModalComponent
import React, { Component } from "react";
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from "reactstrap";
class ModalComponent extends Component {
render() {
const { modal } = this.props;
return (
<div>
<Modal isOpen={modal} toggle={this.toggle}>
<ModalHeader toggle={this.props.toggle}>Halo</ModalHeader>
<ModalFooter>
<Button onClick={this.props.toggle}>Cancel</Button>
</ModalFooter>
</Modal>
</div>
);
}
}
export default ModalComponent;
App
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
modal: false
};
this.toggle = this.toggle.bind(this);
}
toggle() {
const { modal } = this.state;
this.setState({
modal: !modal
});
}
render() {
const { modal } = this.state;
return (
<div>
<Button
type="link"
onClick={this.toggle}
>
Delete account
</Button>
<Modal modal={modal} toggle={this.toggle}/>
</div>
);
}
}
The way you have it now, the modal is setting its own state, but it is still receiving a prop from the parent, which is keeping it open.
Here's an example of one way to do it. Note that both opening and closing is dealt with using the state of the parent, not the state of the child.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
state = { open: false };
toggle = () => {
this.setState({ open: !this.state.open });
};
render() {
return (
<React.Fragment>
something
<Modal show={this.state.open} />
<Child toggle={this.toggle} />
</React.Fragment>
);
}
}
const Child = ({ toggle }) => {
return <button onClick={toggle}>toggle</button>;
};
const Modal = ({ show }) => {
if (show) {
return <h1>I am a modal</h1>;
}
return null;
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
CodeSandbox here.
I want to make when i click button, show modal in different class.
how to make it this?
import React, { Component } from 'react';
import addmodal from './addmodal';
class page extends Component {
...//skip
handleAdd= () =>{
//...how?
}
render(){
return (
<button onClick={this.handleAdd} > Add </button>
)
}
}
import React, { Component } from 'react';
class addmodal extends Component {
// ... skip
render(){
return(
<modal inOpen={this.state.isopen} >
...//skip
</modal>
)
}
}
export default addmodal;
I can't call this addmodal...
how to call modal?
First, your variable naming is terrible. I fixed it for you here. The state that dictates if modal is open must be on the parent component since you have your trigger there. Then, pass it as props to the modal component. Here is the fixed code for you:
import React, { Component } from 'react';
import AddModal from './addmodal';
class Page extends Component {
constructor(){
super();
this.state = { isModalOpen: false };
}
...//skip
handleAdd= () =>{
this.setState({ isModalOpen: true });
}
render(){
return (
<div>
<button onClick={this.handleAdd} > Add </button>
<AddModal isOpen={this.state.isModalOpen} />
</div>
)
}
}
import React, { Component } from 'react';
class AddModal extends Component {
// ... skip
render(){
return(
<modal inOpen={this.props.isOpen} >
...//skip
</modal>
)
}
}
export default AddModal;
what about using trigger. Its very usefull.
<Modal trigger={ <button onClick={() => onCLickPay()} className={someting}> When clicked here modal with show up</button> }`enter code here` >
I have a modal. If I click on the background, I want the modal to go away. If I click on the modal form, I do not want it to go away. Having an issue where trigger events on the form are bubbling up to the container background and closing the div. I want the modal to NOT close if the inner div is clicked.
(clearQuiz makes the modal disappear)
Here is the component:
import React from 'react';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { clearQuiz } from '../../actions/quiz';
import './Quiz.css';
export class Quiz extends React.Component {
handleClose(e) {
e.stopPropagation();
this.props.dispatch(clearQuiz());
}
render() {
let answers = this.props.answers.map((answer, idx) => (
<li key={idx}>{answer}</li>
));
if (this.props.usingQuiz) {
return (
<div className="quiz-backdrop" onClick={e => this.handleClose(e)}>
<div className="quiz-main">
<h2>{this.props.title} Quiz</h2>
<h3>{this.props.currentQuestion}</h3>
<ul>
{answers}
</ul>
</div>
</div>
)
}
else {
return <Redirect to="/" />;
}
}
}
const mapStateToProps = state => ({
usingQuiz: state.currentQuestion,
answers: state.answers,
currentQuestion: state.currentQuestion,
title: state.currentQuiz,
});
export default connect(mapStateToProps)(Quiz);
Don't pass the event at all! Try onClick={()=>this.handleClose()}.
I need to render a modal/lightbox component dynamic into a list array component, but it only renders the last modal content.
How can I turn this modal component dynamic to call it from the main component and populate it with correct data from an object array?
My List component is:
import React, { Component } from 'react';
import LightBox from './LightBox';
class ListPrice extends Component {
constructor(props) {
super(props);
this.state = { isOpen: false };
}
toggleModal = () => {
this.setState({
isOpen: !this.state.isOpen
});
}
render() {
return (
<div>
{this.props.products.map(product => {
return(
<div>
<a key={product.id} onClick={this.toggleModal}>
<h3>{product.title}</h3>
<p>{product.description}</p>
</a>
<LightBox key={product.id} show={this.state.isOpen}
onClose={this.toggleModal}>
{product.modalContent}
</LightBox>
</div>
);
})}
</div>
);
}
}
export default ListPrice;
And my LightBox component is (I removed styles to display short code here):
import React from 'react';
import PropTypes from 'prop-types';
class LightBox extends React.Component {
render() {
if(!this.props.show) {
return null;
}
return (
<div>
<div>
{this.props.children}
<div>
<button onClick={this.props.onClose}>
Close
</button>
</div>
</div>
</div>
);
}
}
LightBox.propTypes = {
onClose: PropTypes.func.isRequired,
show: PropTypes.bool,
children: PropTypes.node
};
export default LightBox;
Thank you for any advice :)
With show={this.state.isOpen} you always display all the modals - only the last one is visible as other modals are displayed behind it.
In order to fix that you must show only the selected dialog. You can store opened dialog in state with construct like this.setState({ openedDialog: product.id }).
Then you can query if the dialog is open by using this.state.openedDialog === product.id. That should do the job.
openModal = (id) = () => {
this.setState({
openedDialog: id
});
}
closeModal = () => {
this.setState({
openedDialog: null
});
}
show={this.state.openedDialog === product.id}
onClick={this.openModal(product.id)}
onClose={this.closeModal}