简体   繁体   中英

Reactjs controlling state in parent from grand child

I have at my top level:

import React from 'react';
import JobList from './JobList';
import RightPanel from './RightPanel';

import JobStore from '../../stores/JobStore';
import LoadJobsScreen from '../../actions/jobs-screen/LoadJobsScreen';
import Modal from '../global/Modal';

export default class JobScreen extends React.Component {

    static contextTypes = {
        executeAction: React.PropTypes.func.isRequired
    };

    componentWillMount() {
        this.toggleModal = this.toggleModal.bind(this);
        this.state = {open: false}
        this.context.executeAction(LoadJobsScreen, this);
    }

    toggleModal() {
        this.setState({
            open: !this.state.open
        });
        console.log(this.state.open);
    }

    render() {
        return (
            <div className="jobs-screen">
                <div className="col-xs-12 col-sm-10 job-list"><JobList /></div>
                <div className="col-xs-12 col-sm-2 panel-container">
                    <div className="right-panel pull-right"><RightPanel /></div>
                </div>
                <Modal open={this.state.open} toggleModal={this.toggleModal} />
            </div>
        );
    }
}

Modal is:

import React from 'react';

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

    render() {
        let open = this.props.open;
        return (
            <div className={'modal fade'+(open ? '' : ' hide')} tabindex="-1" role="dialog">
                <div className="modal-dialog">
                    <div className="modal-content">
                        <div className="modal-header">
                            <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
                            <h4 className="modal-title">{this.props.title}</h4>
                        </div>
                        <div className="modal-body">
                            {this.props.children}
                        </div>
                        <div className="modal-footer">
                            <button type="button" className="btn btn-default" data-dismiss="modal">Close</button>
                            <button type="button" className="btn btn-primary">Save changes</button>
                        </div>
                    </div>
                </div>
            </div>
        )
    }
}

export default Modal;

But I want to open and close it (as well as send data to it later) from within a deeper component:

import React from 'react';
import UrgencyToggle from './UrgencyToggle';
import ApproveButton from './ApproveButton';
import ShippingTable from './ShippingTable';
import DropdownButtonList from '../global/DropdownButtonList';

export default class Job extends React.Component {
    constructor(props) {
        super(props);

    }

    setUrgency(urgency) {
        actionContext.dispatch('SET_JOB_URGENCY', {
            data: urgency
        })
    };

    render() {
        return ( <
            span className = "name" > < img src = "/images/system-icons/pencil.png"
            onClick = {
                this.toggleModal
            }
            width = "13" / > < /span>
        )
    }
};

Obviously this doesn't work because toggleModal is all the way up in JobScreen. How can I execute the function in the grandparent from this depth?

If your JobScreen , JobList , Job and Modal components are designed to be tightly coupled, ie not meant to be separated from each other in future, you could use the JobScreen as a High Order Component to store the state of your modal and passing down the tree as prop a callback function to update this state (I simplified a bit and made some assumptions on missing components) :

export default class JobScreen extends React.Component {

    constructor(props) {
        super(props);
        this.displayName = 'JobScreen'
        this.state = {
            modalOpened: false,
            modalTitle: "",
        }
    }
    componentWillMount() {
        this.context.executeAction(LoadJobsScreen, this);
    }

    toggleModal() {
        this.setState({
            modalOpened: !this.state.modalOpened
        });
    }

    editModalTitle(title) {
        this.setState({
            modalTitle: title
        })
    }

    render() {
        return (
            <div className="jobs-screen">
                <div className="col-xs-12 col-sm-10 job-list">
                    <JobList
                        toggleModal={() => this.toggleModal() /* auto binding with arrow func */}
                        editModalTitle={(title) => this.editModalTitle(title)} />
                </div>
                <Modal
                    open={this.state.modalOpened}
                    title={this.state.modalTitle}/>
            </div>
        );
    }
}

const JobList = (props) => {

    const jobs = [1,2,3]

    return (
        <ul>
            {jobs.map(key => (
                <li key={key}>
                    <Job
                        toggleModal={props.toggleModal}
                        editModalTitle={props.editModalTitle}/>
                </li>
            ))}
        </ul>
    );

}

const Job = (props) => {

    return (
        <span className="name">
            <img
                src="/images/system-icons/pencil.png"
                width="13"
                onClick={(e) => {
                    props.toggleModal(e)
                    props.editModalTitle("new title") //not very efficient here cause we're updating state twice instead of once, but it's just for the sake of the example
                }}/>
        </span>
    );

}

I deliberately not mentions how to modify the modal children this way cause it's an absolute anti-pattern. So, you should definitively look at something like Redux which provides a way to manage the state of your application and dispatch action from wherever you want to update in a 'one way data binding' way. I've the impression you're trying to bypass the React internal mechanisms by using context as an action dispatcher. So, Redux (or another Flux library) will be your best bet here.

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