Khepin
Khepin

Reputation: 949

How to get a child component to control the parent in react?

I'm building a Modal component and want to make sure that it would easily fit a variety of different possible modal designs.

In my case it would have optional header, content and footer. I don't have an issue with this so far, the code looks like:

class ModalHeader extends React.Component {
    render () {
        const children = React.Children.map(this.props.children, _.identity);
        return (
            <div className="modal-header">
                {children}
            </div>
        );
    }
}

class ModalFooter extends React.Component {
    render () {
        const children = React.Children.map(this.props.children, _.identity);
        return (
            <div className="modal-footer">
                {children}
            </div>
        );
    }
}

class ModalContent extends React.Component {
    render () {
        const children = React.Children.map(this.props.children, _.identity);
        return (
            <div className="modal-content">
                {children}
            </div>
        );
    }
}

class Modal extends React.Component {
    render () {
        var header, footer, content = null;
        React.Children.map(this.props.children, function(child){
            if (child.type === ModalHeader) {
                header = child;
            }
            if (child.type === ModalFooter) {
                footer = child;
            }
            if (child.type === ModalContent) {
                content = child;
            }
        });

        return (
            <div>
                {header}
                {content}
                {footer}
            </div>
        );
    }
}

Now comes the issue of closing the modal. I want it to be possible to close the modal from clicking an element that would be in any of the sub components, no matter, to the left or the right or anything and potentially nested deeply in the markup specific to that component.

Having a component that would wrap a piece of markup be it an X, a close icon or a button, making sure that when that element is clicked, the whole modal is closed.

class ModalCloser extends React.Component {
    render () {
        const children = React.Children.map(this.props.children, _.identity);
        return (
            <div onClick={this.close} className="modal-closer">
                {children}
            </div>
        );
    }

    close () {
        // no idea what goes here!!
    }
}

There doesn't seem to be in React any easy way for a child component to communicate with the parent.

I would be fine with passing a prop to that closer element that would be the callback function to close the main modal, but at the place where I would be defining the modal, I don't see any way that it would be available:

<Modal>
    <ModalHeader>
        <div>
            <header>
                <h1>Title!!</h1>
                <div class="float-right">
                    <ModalCloser closeHandler={???}>
                        X
                    </ModalCloser>
                </div>
            </header>
        </div>
    </ModalHeader>
    <ModalContent>
        ...
    </ModalContent>
</Modal>

Alternatively, I see that I could recursively traverse all the descendants and maybe do something to all descendants of type ModalCloser but I also don't really see a way to do that available.

What would be a good solution allowing to pass such a sub-component as a child or descendant to keep the layout flexibility while giving it the possibility to close the modal in such a case?

Upvotes: 0

Views: 2834

Answers (1)

Lyubomir
Lyubomir

Reputation: 20027

Pass a callback from the parent to the child and on close modal, call the callback in the child.

Here is a simpler version of your code, where modalClose is the callback that's being passed to the parent from the child. You can also test it in jsfiddle.

class ModalCloser extends React.Component {

  render () {
    return (
      <div onClick={this.props.close}>
        {this.props.children}
      </div>
    );
  }
}

class Main extends React.Component {

  modalClose() {
    alert('closing')
  }

  render() {
    return (<ModalCloser close={this.modalClose}>X</ModalCloser>);
  }
}

ReactDOM.render(
  <Main />,
  document.getElementById('container')
);

In your jsfiddle you have defined the callback in the render method, therefore it was executed on every rerender of the component. I would strongly recommend you to read Thinking in React multiple times - it will answer all your questions.


Pseudocode

<Parent>
  modalClose () { 
   console.log('modal closed...') 
  }
  <Child modalClose={modalClose} />
<Parent/> 

Now in your < Child /> whenever modal closes:

closeHandler = { this.props.modalClose }

Upvotes: 4

Related Questions