Aamir Jaffer Aamir Jaffer - 1 month ago 7
React JSX Question

Modify a child state from parent "one hit"

I've been searching for a few days and can't seem to get this to work and it's driving me insane!

The aim is an accordion with the addition of an "Expand All"/"Collapse All" button. I managed to get this to work at one point by passing an expandAll property to the child component through props and updating the expand state of the child in componentWillReceiveProps, but if the parent component is set to auto-fetch data on a timer, then it meant that the child state was updated to collapse or expand all the panels after the second fetch which isn't the required behaviour.

class App extends React.Component {

constructor(props){
super(props);
this.state = {
"teams": [{ Name: "Liverpool", Players: ["Lovren", "Coutinho"]}, {Name:"Arsenal", Players: ["Ozil", "Welbeck"]},{Name: "Chelsea", Players: []}],
toggleAll: false,
toggleText: "Expand All"
}
}

_toggleAll(event){
event.preventDefault();
let text = "Expand All";
if (!this.state.toggleAll === true){
text = "Collapse All"
}
this.setState({toggleAll: !this.state.toggleAll, toggleText: text});
console.log("toggleAll", this.state.toggleAll);
}


render() {
return (
<div className="headers">
<button onClick={this._toggleAll.bind(this)}>{this.state.toggleText}</button>
{this.state.teams.map((team, index) =>
<Header className="" key={index} title={team.Name} players={team.Players} />
)}

</div>
);
}
}

class Header extends React.Component {
constructor(props){
super(props);
this.state = {
expanded: false
}
}

componentWillMount(){
console.log(this.props.title);
console.log(this.props.players);
}

_expand(event){
event.preventDefault();
this.setState({expanded: !this.state.expanded});
}

render() {
let players;
if(this.state.expanded)
{
players= <ul>
{this.props.players.map((player, index) => <li key={index}>{player}</li>)}
</ul>
}

return (
<div>

<div className="header" >
<p onClick={this._expand.bind(this)}>{this.props.title}</p>

</div>
{players}
</div>

);
}
}


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


Fiddle provided below
https://jsfiddle.net/aamirjaffer/x52bhdq6/

Answer

You need to pass a prop from the the App component to give the header it's "collapseAll" state. I updated the rendering of the header in App like so:

<Header 
  className = ""
  key={index}
  title={team.Name}
  players = {team.Players}
  allExpanded={this.state.toggleAll}
/>

Then the Header component needs to listen for that prop change on conponentWillReceiveProps and set it's own state accordingly like so:

componentWillReceiveProps(nextProps) {
    if (nextProps.allExpanded !== this.props.allExpanded) {
    this.setState({ expanded: nextProps.allExpanded });
  }
}

I forked your fiddle made, those changes and it all works just fine: https://jsfiddle.net/xkp4snmx/

Comments