Highlighting one object at a time with React
Great start! Let's walk through some problems in your code first.
Separation of concerns
There's no need to create your entire nested structure inside your top-most component. This is very contrived:
for (i=0; i < this.props.menuitems.length; i++) { if(this.props.menuitems[i].section !== lastSection) { var section = this.props.menuitems[i].section; var items = []; for (j=0; j < this.props.menuitems.length; j++) { if(this.props.menuitems[j].section == section) { var itemName = this.props.menuitems[j].name; items.push(<SectionItem title={itemName} key={itemName} />); }; } sections.push(<Section title={section} items={items} key={section} />); lastSection = section; }}
Quite on the contrary. You should try and make each component responsible for the rendering of their own piece of information. We could improve this if we first treated your data. The problem is that your sections are not nested. What if, instead of this...
var MENU_ITEMS = [ {section: "About", name: "Hey", key: "Hey", selected: true}, {section: "About", name: "No", key: "No", selected: false}, {section: "About", name: "Way", key: "Way", selected: false}, {section: "People", name: "Cakewalk", key: "Cakewalk", selected: false}, {section: "People", name: "George", key: "George", selected: false}, {section: "People", name: "Adam", key: "Adam", selected: false}, {section: "Projects", name: "Pirate raid", key: "Pirate raid", selected: false}, {section: "Projects", name: "Goosehunt", key: "Goosehunt", selected: false},];
We had this:
var sections = [ { name: "About", items: [ {name: "Hey", key: "Hey", selected: true}, {name: "No", key: "No", selected: false}, {name: "Way", key: "Way", selected: false} ] },{ name: "People", items: [ {name: "Cakewalk", key: "Cakewalk", selected: false}, {name: "George", key: "George", selected: false}, {name: "Adam", key: "Adam", selected: false} ] },{ name: "Projects", items: [ {name: "Pirate raid", key: "Pirate raid", selected: false}, {name: "Goosehunt", key: "Goosehunt", selected: false} ] }];
Then we could simplify Accordion
quite a bunch. We simply render one Section
for each section
:
var Accordion = React.createClass({ render: function() { return ( <div className="main"> {this.props.sections.map(function(section){ return <Section key={section.name} section={section}/> })} </div> ); }});
Likewise, Section and SectionItem become quite simpler.
var Section = React.createClass({ handleClick: function(){ this.setState({ open: !this.state.open, class: this.state.open ? "section" : "section open" }); }, getInitialState: function(){ return { open: false, class: "section" } }, render: function() { return ( <div className={this.state.class}> <div className="sectionhead" onClick={this.handleClick}>{this.props.section.name}</div> <div className="articlewrap"> <div className="article"> {this.props.section.items.map(function(item){ return <SectionItem key={item.name} item={item}/> })} </div> </div> </div> ); }});var SectionItem = React.createClass({ handleClick: function(){ this.setState({ currentItem: this, active: !this.state.active, class: this.state.active ? "sectionitem" : "sectionitem active" }); }, getInitialState: function(){ return { active: false, class: "sectionitem" } }, render: function() { return ( <div className={this.state.class} onClick={this.handleClick}>{this.props.item.name}</div> ); }});
Propagating state changes
Now, to your original question. In a more complex application, you could benefit from something more robust like Flux. However, for now, following the techniques exposed in Thinking in React should solve your problem.
Indeed, one good way is to bring your state of "what is open" to the Accordion
component. You simply need to let your parent know that something is being clicked. We can do that through a callback passed as a prop
.
So, Accordion
could have an openSection
state, and an onChildClick
that receives the clicked section's name. It needs to pass onChildClick
to each Section
.
var Accordion = React.createClass({ getInitialState: function() { return { openSection: null }; }, onChildClick: function(sectionName) { this.setState({ openSection: sectionName }); }, render: function() { return ( <div className="main"> {this.props.sections.map(function(section){ return <Section key={section.name} onChildClick={this.onChildClick} open={this.state.openSection===section.name} section={section}/> }.bind(this))} </div> ); }});
And Section
simply calls this function when clicked, passing in it's own name.
var Section = React.createClass({ handleClick: function(){ this.props.onChildClick(this.props.section.name); }, render: function() { var className = this.props.open ? "section open" : "section" return ( <div className={className}> <div className="sectionhead" onClick={this.handleClick}>{this.props.section.name}</div> <div className="articlewrap"> <div className="article"> {this.props.section.items.map(function(item){ return <SectionItem key={item.name} item={item}/> })} </div> </div> </div> ); }});
You can extrapolate this solution to the SectionItem
problem.
The resulting codepen is here: http://codepen.io/gadr90/pen/wamQXG?editors=001
Good luck on learning React! You are on the right path.