1

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;
Marko Gresak
  • 7,950
  • 5
  • 40
  • 46
  • 1
    Feels like you need to do `stopPropagation` somewhere. Maybe your state is changing to `false` and then `true` again due to event bubbling. If you can create a codesandbox/stack snippet to reproduce your problem, someone could debug and help you. – maazadeeb Apr 23 '19 at 17:35

3 Answers3

0

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 :)

0

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} />
Carlos Querioz
  • 277
  • 3
  • 7
0

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.

Edit xlxp61vy5q

Hope this helps.

Ujwal Agrawal
  • 454
  • 7
  • 8
  • If the solution is working for you then kindly accept the solution so that it could be beneficial for others. Thanks !! – Ujwal Agrawal Apr 24 '19 at 09:07