Denis Yakovenko
Denis Yakovenko

Reputation: 3535

Implement 'toggle class' functionality on a list of elements with React

I'm struggling with implementing 'toggle class' functionality in React. I have the NavSidebar component which is an unordered list ul. My NavListItem component is list item li.

When the list item is clicked I need to give the class name active to the clicked li and other list elements should get an empty class name.

Here's what I've come up with by far:

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

  setActive(bool) {
    this.setState({ active: bool });
  }

  render() {
    let index = this.props.key;
    let item = this.props.item;
    return (
      <li className={this.state.active ? 'active' : ''}
          onClick={this.setActive(this.props.setActive).bind(this)} >
        <a href={'#'+item}>{item}</a>
      </li>
    );
  }
}

class NavSidebar extends React.Component {

  constructor(props) {
    super(props);
  }

  render () {

    let items = this.props.navItems.map( (item, index) => {
      return (
        <NavListItem key={index} item={item} setActive={true} />
      );
    });

    return (
        <div className="col-sm-2">
          <nav className="nav-sidebar">
            <ul className="nav">
              {items}
              <li className="nav-divider" />
              <li>
                <a href="#"><i className="glyphicon glyphicon-home"></i> Back Home</a>
              </li>
            </ul>
          </nav>
        </div>
    );
  }
}

NavSidebar.propTypes = {
  navItems: React.PropTypes.array
};

Unfortunately, this doesn't work, it never even touches the className of the elements and I can't come up with the working solution.

Could you please help me with that?

Upvotes: 3

Views: 5541

Answers (1)

manji
manji

Reputation: 47978

There is no error message here?

onClick={this.setActive(this.props.setActive).bind(this)}

Because you are binding undefined, result of calling this.setActive(this.props.setActive)

and even if you remove the bind, it will not work as the click handler will be undefined. Also, the state should never be modified inside render.

In any case, because you want to update more than one item (the new selected one and the old one), the parent should be responsible of this and should:

  • activate the clicked item
  • deactivate all others
  • re-render the list

Something like this (not tested):

class NavListItem extends React.Component {
  constructor(props) {
    super(props);
  }

  setActive(bool) {
    this.props.onItemActive(this.props.item);
  }

  render() {
    let item = this.props.item;
    return (
      <li className={this.props.active ? 'active' : ''}
          onClick={this.setActive.bind(this)} >
        <a href={'#'+item}>{item}</a>
      </li>
    );
  }
}

class NavSidebar extends React.Component {

  constructor(props) {
    super(props);
    this.state = {activeItem: null};
  }

  onItemActive(item) {
    this.setState({activeItem: item};
  }

  render () {
    let self = this;
    let items = this.props.navItems.map( (item, index) => {
      return (
        <NavListItem key={index} item={item} 
                     onItemActive={self.onItemActive.bind(self)} 
                     active={item === self.state.activeItem} />
      );
    });

    return (
        <div className="col-sm-2">
          <nav className="nav-sidebar">
            <ul className="nav">
              {items}
              <li className="nav-divider" />
              <li>
                <a href="#"><i className="glyphicon glyphicon-home"></i> Back Home</a>
              </li>
            </ul>
          </nav>
        </div>
    );
  }
}

NavSidebar.propTypes = {
  navItems: React.PropTypes.array
};

Upvotes: 5

Related Questions