-1

I know this question has been asked before but the answers always seemed to hint at a problem unrelated to this scenario and I just don't see it.

I have a parent component which passes a property to a child component's state. Depending on the value of this passed property the child initially shows its view or not. Here is the way I thought React works:

When a parent's (or a component in general) state changes, it will be re-rendered. When it gets re-rendered, all of its children receive their props again (i.e. the constructor of the child is also invoked?). But that is apparently not how it works in this example.

Parent component:

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

        this.state = { 
            showModal: false
        };

        this.showSelectFolderModal = this.showSelectFolderModal.bind(this);
    }

    showSelectFolderModal()
    {
        this.setState({ showModal: true });
    }

    render()
    {       
        console.log("Rendering table with showModal = " + this.state.showModal);
        return (
            <div>
                <table id="files-table" class="table">
                    <tr>
                <td>
                    ...
                    <span class="float-right">
                        <FileActionMenu 
                            showSelectFolderModal={ this.showSelectFolderModal } />
                    </span>
                </td>
            </tr>
                </table>
                <SelectFolderModal 
                    show={ this.state.showModal }/>
            </div>
        );
    }
}

FileActionMenu component child:

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

    render()
    {
        return (
            <div class="dropdown">
            <button class="btn dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                ...
            </button>
            <div class="dropdown-menu dropdown-menu-right">            
                <button class="dropdown-item" type="button" onClick={ () => this.props.showSelectFolderModal() }>...</button>                  
            </div>
        </div>
        );
    }
}

SelectFolderModal component child:

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

        console.log("Received props from table");
        console.log(this.props);
        this.state = { show: this.props.show };
    }

    closeModal()
    {
        this.setState({ show: false });
        // do something post close
    }

    render()
    {
        console.log("Rendering modal with show status: " + this.state.show);

        return (
            <div id="select-folder-modal" class="modal { this.state.show ? 'show' }" tabindex="-1" role="dialog">
            <div class="modal-dialog" role="document">
                <div class="modal-content">
                    <div class="modal-header">
                        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                            <span aria-hidden="true">&times;</span>
                        </button>
                    </div>
                    <div class="modal-body">
                        ...
                    </div>
                    <div class="modal-footer">
                        <button type="button" class="btn btn-secondary" data-dismiss="modal" onClick={ this.closeModal }>...</button>
                    </div>
                </div>
            </div>
        </div>
        );
    }
}

My initial console output is this:

Rendering table with showModal = false
Received props from table
{show: false}
Rendering modal with show status: false

Okay, this makes sense. But when i click on the button in FileActionMenu i get the following output:

Rendering table with showModal = true
Rendering modal with show status: false

So the state of the parent is not passed as props to the child when the parent is updating! But I thought this is how it works, can someone shed some light on this for me?

MTCoster
  • 5,868
  • 3
  • 28
  • 49
Furious Gamer
  • 359
  • 1
  • 3
  • 16
  • Possible Duplicate of https://stackoverflow.com/questions/43779411/reactjs-setting-state-from-props-using-setstate-in-child-component/43780546#43780546 – Shubham Khatri Jan 03 '19 at 11:16

3 Answers3

0

No, your thoughts are not correct. Components' constructor are not supposed to get called every time they render/update. They get called once they are being mounted.

Back to your example, the SelectFolderModal should be stateless. Actually the parent maintains the app's "state". There is nothing preventing you from using "props" in render method:

  1. In SelectFolderModal's render method, use the props not state as this component is supposed to be stateless.

  2. Also you need another prop for this component: closeModal, which parent should pass in a callback method to close the modal by setting the state.

    this.setState({ showModal: false });
    
frogatto
  • 28,539
  • 11
  • 83
  • 129
  • But SelectFolderModal has a close button, which changes its view and also some actions have to be done. I have been struggling with the lifecycle of a modal called by an unrelated component for quite a bit. I am really not sure where to handle the states and i feel like its overkill for my project to get into Redux. Okay so the constructor is not called every time, makes sense. I guess i will have to look at some mount related methods? – Furious Gamer Jan 03 '19 at 11:29
  • @FuriousGamer The parent should maintain the state. As I updated my answer, the parent should pass in two things: 1. `show` prop and 2. a callback for `closeModal`. – frogatto Jan 03 '19 at 11:35
0

There is a small error in your code. You are setting the parent props(show props) into the child state in constructor which will work initially. Then each time the parent props changes, the child component receives the changed props through shouldComponentUpdate.

So, in your code you can access the parent props (show) like this:

  1. Add a function similar to showSelectFolderModal to change the show value to false
  2. Then pass the function to the child component and assign it to the button instead of closeModal function.

class FileListTable extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      showModal: false
    };

    this.showSelectFolderModal = this.showSelectFolderModal.bind(this);
    this.hideSelectFolderModal = this.hideSelectFolderModal.bind(this);
  }

  showSelectFolderModal() {
    this.setState({ showModal: true });
  }

  hideSelectFolderModal() {
    this.setState({ showModal: false });
  }

  render() {
    console.log(this.state);
    return (
      <div>
        <table id="files-table" class="table">
          <tr>
            <td>
              Main Table
              <span class="float-right">
                <FileActionMenu
                  showSelectFolderModal={this.showSelectFolderModal}
                />
              </span>
            </td>
          </tr>
        </table>
        <SelectFolderModal
          show={this.state.showModal}
          hideSelectFolderModal={this.hideSelectFolderModal}
        />
      </div>
    );
  }
}

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

  render() {
    return (
      <div class="dropdown">
        <div class="dropdown-menu dropdown-menu-right">
          <button
            class="dropdown-item"
            type="button"
            onClick={() => this.props.showSelectFolderModal()}
          >
            event button
          </button>
        </div>
      </div>
    );
  }
}

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

  render() {
    console.log(this.props.show);
    return (
      <div
        id="select-folder-modal"
        class={`modal ${this.props.show ? "show" : ""}`}
        tabIndex="-1"
        role="dialog"
      >
        <div class="modal-dialog" role="document">
          <div class="modal-content">
            <div class="modal-header">
              <button
                type="button"
                class="close"
                data-dismiss="modal"
                aria-label="Close"
              >
                <span aria-hidden="true">&times;</span>
              </button>
            </div>
            <div class="modal-body">...</div>
            <div class="modal-footer">
              <button
                type="button"
                class="btn btn-secondary"
                data-dismiss="modal"
                onClick={() => this.props.hideSelectFolderModal()}
              >
                hide
              </button>
            </div>
          </div>
        </div>
      </div>
    );
  }
}

ReactDOM.render(<FileListTable />, document.getElementById("root"));

Here is the live demo

Hope it helps :)

Arun AK
  • 4,353
  • 2
  • 23
  • 46
0

A small code change might help you in solving your problem.

Please do change your callback function as below:

showSelectFolderModal()
{
    this.setState({ showModal: !this.state.showModal });
}

In addition, send this function as a prop to SelectFolderModal component. Because you can call this function rather than calling closeModal function.

Please avoid using multiple states to perform a single action. There is no point in using a state variable in SelectFolderModal as per your code.

  • You should not use the state directly for setting the next state. Instead of passing `setState` an object literal you should pass in a function which receives the previous state as its first argument. – frogatto Jan 03 '19 at 11:40
  • Hey @HiI'mFrogatto thanks for sharing mate. I really do appreciate if you edit my answer :) – Kosalram Ramakrishnan Jan 03 '19 at 11:46
  • Please read the documentation for [`setState`](https://reactjs.org/docs/react-component.html#setstate). – frogatto Jan 03 '19 at 11:51