Flo
Flo

Reputation: 35

Set clicked child component active while all of its siblings inactive?

Sorry if the phrasing of my question is unclear. I'm new to React and making an app with three different screens/pages. There should be a footer that follows the state of the payment process thanks to 3 boxes. The box that is being selected should show up in another color (here I've used a solid blue border).

Three boxes, the first one being selected

When clicking on another box (box 3 for example), I would like the third box to be selected and the other two boxes to be unselected. In order to achieve this, I've set up a state.active in the child element. If the state is active, then a different style is applied to the element (the blue border).

I have two components, "ProgressBar" being the parent, and "ProgressElement" being the child. Here is what the code looks like :

class ProgressElement extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            selected: false
        }
        this.handleClick = this.handleClick.bind(this)
    }

    componentDidMount() {
        if (this.props.eleType === "pattern") {
            this.setState({selected: true})
        }
    }

    handleClick() {
        if (!this.state.selected) {
            this.setState({selected: true})
        }
        if (this.state.selected) {
            this.setState({selected: false})
        }
    }


    render() {
        let eleNumber = NaN
        if (this.props.eleType === "pattern") {
            eleNumber = 1;
            this.state.selected === true;
        }
        if (this.props.eleType === "gift") {
            eleNumber = 2;
        }
        if (this.props.eleType === "personal") {
            eleNumber = 3;
        }

        if (this.state.selected) {
            return <div className="square-box selected"
                        onClick={this.handleClick}>{eleNumber}</div>
        } else {
            return <div className="square-box"
                        onClick={this.handleClick}>{eleNumber}</div>
        }

    }

}

class ProgressBar extends React.Component {
    render() {
        return <div className="d-flex custom-progress-bar">
            <ProgressElement eleType="pattern"/>
            <ProgressElement eleType="gift"/>
            <ProgressElement eleType="personal"/>
        </div>
    }
}

It's working fine, but my only problem is that I don't know how to make the other boxes unselected. For that, I would need the other sibling elements to be aware of the state of the other ones. I've thought of two ways to do this but have been unable to make one of these work so far :

Could anyone help me with this ? I know this is probably basic stuff but I'm having a hard time with React. Thanks for reading me and thanks in advance.

Upvotes: 1

Views: 741

Answers (2)

Zouhair Dre
Zouhair Dre

Reputation: 628

Try and apply this, I did not test the code myself so make sure that you apply the logic to your code (I'm rusted with classes)

 class ProgressElement extends React.Component {

    render() {

        if (this.props.eleType === this.props.selected) {
            return <div className="square-box selected"
            onClick={this.props.act({selectedValue:""})}>{this.props.eleType}</div>
        } else {
            return <div className="square-box"
                        onClick={this.props.act({selectedValue:this.props.eleType})}>{this.props.eleType}</div>
        }

    }

}

class ProgressBar extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            selectedValue: ""
        }
    }
    updateSelected = (selected) => {
        this.setState(selected);
    }


    render() {
        return <div className="d-flex custom-progress-bar">
            <ProgressElement eleType="pattern" act={this.updateSelected} selected={this.state.selectedValue}/>
            <ProgressElement eleType="gift" act={this.updateSelected} selected={this.state.selectedValue}/>
            <ProgressElement eleType="personal" act={this.updateSelected} selected={this.state.selectedValue}/>
        </div>
    }
}

Upvotes: 1

Yevhen Horbunkov
Yevhen Horbunkov

Reputation: 15530

You need to keep track of currently active child within the parent component and pass it down as a prop:

const { Component } = React,
      { render } = ReactDOM,
      rootNode = document.getElementById('root')
      
class ProgressElement extends React.Component {
   handleClick = eleType => this.props.onSetActive(eleType)

   render(){  
      return (
        <div 
          className={this.props.eleType === this.props.activeEleType && 'active'}
          onClick={() => this.handleClick(this.props.eleType)}
        >
          {this.props.eleType}
        </div>
      )
   }
}

class ProgressBar extends React.Component {
    state = {activeEleType: null}
    onSetActive = activeEleType => this.setState({
      activeEleType
    })
    
    render() {
        return (
          <div className="progress-bar">
            {
              ['pattern', 'gift', 'personal'].map(key => (
                <ProgressElement 
                  activeEleType={this.state.activeEleType}
                  eleType={key}
                  key={key}
                  onSetActive={this.onSetActive}
                />
              ))
            }
          </div>
        )
    }
}

render (
  <ProgressBar />,
  rootNode
)
.progress-bar {
  display: flex;
}

.progress-bar>div {
   width: 100px;
   height: 100px;
   background: grey;
   color: #fff;
   display: flex;
   align-items: center;
   justify-content: center;
   margin: 10px;
   cursor: pointer;
}

.progress-bar>div.active {
  border: 2px solid blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>

Upvotes: 2

Related Questions