简体   繁体   中英

React JS props are not passed to child component on parent rerender

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 (ie 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?

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 });

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM