zappee
zappee

Reputation: 22668

change child components' state from parent (exclusive clickable box component with React)

I am writing a Box component which

I wrote this component and it works fine.

Now I would like to improve my component. I need to show multiply Box on my parent page and when I click on one Box I would like to reset the state of the rest Box components displays on parent so when I move mouse between the Boxes their styles need to be change and when I click one of them then the selected Box's border needs to be change.

This is the Box component:

const STYLE = {
  boxMouseOver: {...},
  boxMouseOut: {...},
  boxOn: {...},
}

export default class ClickableBox extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      mouseOver: false,
      turnOn: false
    }
  }

  handleMouseOver = () => {
    this.setState({
      mouseOver: true
    })
  }

  handleMouseOut = () => {
    this.setState({
      mouseOver: false
    })
  }

  handleOnClick = () => {
    this.setState({
      turnOn: !this.state.turnOn
    });

    this.props.resetStateCallback();
  }

  resetState = () = => {
    this.setState({
      turnOn: false
    });
  }

  render () {
    let style = this.state.mouseOver ? STYLE.boxMouseOver : STYLE.boxMouseOut
    style = this.state.turnOn ? STYLE.boxOn : style

    return (
      <div style={style} onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut} onClick={this.handleOnClick}>
        <span style={STYLE.title}>{this.props.title}</span>
        <br/>{this.state.turnOn ? "on" : "off"}
      </div>
    )
  }
}

And this is the parent component:

export default class GroupOfBoxes extends React.Component {
  reset () {
    alert('reset')

    // **** I need help for this part ****
    // Need to be called resetState() method on
    // ClickableBox components shows on this page
    for (currentBox : ?????) {
       if (currentBox != selected Box??) {
          currentBox.resetState()
       }
    }
  }

  render () {
    return (
      <div>
        <Grid>
          <Row className='show-grid'>
            <Col sm={12}>
              <ClickableBox title='1' resetStateCallback={this.reset} />
              <ClickableBox title='2' resetStateCallback={this.reset} />
              <ClickableBox title='3' resetStateCallback={this.reset} />
              <ClickableBox title='4' resetStateCallback={this.reset} />
            </Col>
          </Row>
          <Row className='show-grid'>
            <Col sm={12}>
              <ClickableBox title='5' resetStateCallback={this.reset} />
              <ClickableBox title='6' resetStateCallback={this.reset} />
              <ClickableBox title='7' resetStateCallback={this.reset} />
              <ClickableBox title='8' resetStateCallback={this.reset} />
            </Col>
          </Row>
        </Grid>
      </div>
    )
  }
}

I do not know hot to change the Box component state from the parent page because I need to have references of my Box components on the parent page and call ClickableBox.resetState() method on each ClickableBox components from the GroupOfBoxes.reset() method.

---- UPDATE ----

After I read more React related docs I was able to make my code works but I think that my solution is far from the optimal. I would appreciate any help for optimalisation.

So I use ref property to get references to the my Box components and I wrote lots of conditions to decide which Box components need to be reset:

First, I made change on the ClickableBox component, I added a 'this' param to the resetStateCallback method call:

handleOnClick = () => {
  this.setState({
    turnOn: !this.state.turnOn
  });

  this.props.resetStateCallback(this);
}

Then I added the ref keys to the parent component:

<ClickableBox ref='box1' title='1' body='...' footer='...' resetStateCallback={this.reset} />
<ClickableBox ref='box2' title='2' body='...' footer='...' resetStateCallback={this.reset} />
...

Finally I modified the reset() method on parent component:

(this method looks so ugly so please help me to optimalize it if you can)

reset(comp) {
  if (comp.props.title === '1') {
    this.refs.box2.resetState()
    this.refs.box3.resetState()
    this.refs.box4.resetState()
    this.refs.box5.resetState()
    this.refs.box6.resetState()
    this.refs.box7.resetState()
    this.refs.box8.resetState()
  } else if (comp.props.title === '2') {
    this.refs.box1.resetState()
    this.refs.box3.resetState()
    this.refs.box4.resetState()
    this.refs.box5.resetState()
    this.refs.box6.resetState()
    this.refs.box7.resetState()
    this.refs.box8.resetState()
  } else if (comp.props.title === '3') {
    this.refs.box1.resetState()
    this.refs.box2.resetState()
    this.refs.box4.resetState()
    this.refs.box5.resetState()
    this.refs.box6.resetState()
    this.refs.box7.resetState()
    this.refs.box8.resetState()
  } else if (comp.props.title === '4') {
    ...
  } else if (comp.props.title === '5') {
    ...
  } else if (comp.props.title === '6') {
    ...
  } else if (comp.props.title === '7') {
    ...
  } else if (comp.props.title === '8') {
    ...
  }
}

Upvotes: 2

Views: 132

Answers (1)

Daniel Apt
Daniel Apt

Reputation: 2638

I would wrap all the boxes inside BoxWrapper. BoxWrapper has two properties in its state: mouseOverIndex and clickedIndex.

It passes this state down to its children. That way, all the logic is held in the wrapper, and Box does not need to know of the state of its siblings.

See https://jsfiddle.net/20kpgjcj/3/

Upvotes: 0

Related Questions