Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Highlighting one object at a time with React

I'm pretty new to both React and Javascript, and I've started learning it by building a simple collapsible menu. It works as expected, except for that I can't figure out how to only highlight one item at a time. I suspect that the onClick method and state has to be owned by a higher level class than the sectionItem, but I'm really stuck in how to make this work. My first instinct would then be to iterate over all items in the menu every time something is clicked, and make sure that all other items are switched to active=false.

Is this the right way to think about this? Could someone explain how states work in React in this context, and how I should implement it here?

The entire code is available here: Menu on codepen.io

This is the code for the item I want to make highlightable. I've not yet been able to implement that only one item should be highlighted at once.

var SectionItem = React.createClass({
  handleClick: function(){
    if(!this.state.active) {
      this.setState({
        currentItem: this,
        active: true,
        class: "sectionitem active"});
    }
  },
  getInitialState: function(){
     return {
       active: false,
       class: "sectionitem"
     }
  },
  render: function() {
    return (
        <div className={this.state.class} onClick={this.handleClick}>{this.props.title}</div> 
    );
  }
});
like image 722
aerugo Avatar asked Jul 07 '15 23:07

aerugo


People also ask

How do you highlight text in Reactjs?

To highlight text using React, we can create our own component where we check whether each word matches what we want to highlight. to create the Highlighted component to highlight the text set as the highlight option.

How do I highlight a selected button in React?

Keep a new state such as "selectedButton" then set this state with clicked button's id. Now, Look for if a button's "id" is equal to the "selectedButton" state. If yes, highlight it with a conditional class.

Can I pass an object as a prop React?

Use the spread syntax (...) to pass an object as props to a React component, e.g. <Person {... obj} /> . The spread syntax will unpack all of the properties of the object and pass them as props to the specified component.

What is ${ In React?

the ${} is the syntax for variables (or other code to be executed) inside template literals (`).


1 Answers

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.

like image 166
Guilherme Rodrigues Avatar answered Oct 23 '22 10:10

Guilherme Rodrigues