Ben Davis
Ben Davis

Reputation: 57

How do I control the state of this child component upon a button click in its parent?

I'm struggling to figure out the best way to structure two React components on a site that I'm building.

I have a "book-review" component that should display a respective modal (by adding the "is-active" class to <div className="modal" />) when a review is clicked. For simplicity's sake, let's say that on this page there's a button with an onClick that opens a single modal. The background of this modal takes up the whole screen, so when the background of the modal is clicked, the modal should close.

Closing the modal is easy enough – I just bind the _closeModal function to the modal background. But how do I open a modal from within the book review?

modal.js:

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

    this.state = {
      modalOpen: false,
    };
  }

  render() {
    return (
      <div className={`modal ${this.state.modalOpen ? "is-active" : ""}`}>
        <div className="modal-background" onClick={this._closeModal}>
      ...
        </div>
      </div>
    )
  }

  _closeModal = () => {
    this.setState({
      modalOpen: false,
    })
  }
}

book-review.js:

class BookReview extends React.Component {
  render() {
    return (
      <div>
        <Modal />
        <button onClick={/* open the modal */}>Open Modal</button>
      ...
      </div>
    )
  }
}

I feel that I should control modal state from within its own component, not the book review. The book review will already have a state object with lots of information, so my intuition says that it will get very messy to track all of the modal states as well.

In other threads, people have said that you shouldn't try to control child state from a parent component. The main problem is that I need _closeModal to be inside the child component (because the modal background takes up the whole screen) and _openModal to be inside the parent component (because the modal won't even be visible until this function is called). Any advice is appreciated!

Upvotes: 2

Views: 117

Answers (3)

reactdesign
reactdesign

Reputation: 307

I would suggest to keep the logic of opening and closing of modal in the parent component. So the BookReview component has full control of it. Modal only know that if a prop is set to true the it should display the Modal. here is modified solution:

import React from 'react';

class Modal extends React.PureComponent {
    render() {
        return (
            <div className={`modal ${this.props.isOpen ? "is-active" : ""}`}>
                <div className="modal-background" onClick={this.props.closeModal}>
                    this is a modal
                </div>
            </div>
        )
    }
}

class BookReview extends React.Component {

    constructor(props){
        super(props);
        this.state = {
            isOpen:false
        };
        this.openModal = this.openModal.bind(this);
        this.closeModal = this.closeModal.bind(this);
    }

    openModal(){
        this.setState({isOpen: true});
        console.log('modal opened');

    }

    closeModal(){
        this.setState({isOpen:false});
        console.log('modal closed');
    }

    render() {
        return (
            <div>
                <Modal isOpen={this.state.isOpen} closeModal={this.closeModal}/>
                <button onClick={this.openModal}>Open Modal</button>
            </div>
        )
    }
}

export default BookReview;

I also assume that there would be a parent component rendering list of BookReview. So you can pass a bookId prop in BookReview, and use the BookReview parent to control which BookReview's modal is being displayed.

Upvotes: 0

Ezequiel Fernandez
Ezequiel Fernandez

Reputation: 1074

in Modal component add a method to open modal

openModal(){
    // set modal open
}

in Book review component:

<modal ref="modal" />
<button onClick={() => this.refs.modal.openModal()}>Click</button>

Upvotes: 0

ravibagul91
ravibagul91

Reputation: 20755

You should have state to open modal in BookReview component, and pass it to child component as props

class BookReview extends React.Component {
  state={modalOpen: false}

  _openModal = () => {
     this.setState({modalOpen:true})
  }
  render() {
    return (
      <div>
        <Modal isOpen={this.state.modalOpen}/>
        <button onClick={this._openModal}>Open Modal</button>
      ...
      </div>
    )
  }
}

In child component, you should use props from parent to open modal, and componentDidUpdate for subsequent props change.

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

    this.state = {
      modalOpen: props.isOpen,
    };
  }

  componentDidUpdate(prevProps) {   //This will take care of props change from parent
   if (prevProps.isOpen !== this.props.isOpen) {
     this.setState({modalOpen:this.props.isOpen},()=>console.log(this.state.modalOpen))
   }
  }

  render() {
    return (
      <div className={`modal ${this.state.modalOpen ? "is-active" : ""}`}>
        <div className="modal-background" onClick={this._closeModal}>
      ...
        </div>
      </div>
    )
  }

  _closeModal = () => {
    this.setState({
      modalOpen: false,
    })
  }
}

Upvotes: 2

Related Questions