3

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?

1 Answer 1

5

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
};
Sign up to request clarification or add additional context in comments.

7 Comments

Could you explain what is this.props.onActivate(this.props.item);? And it throws cannot read 'props' of undefined at this line.
this.props.onActivate is the callback belonging to the parent passed down to children. this.props.onActivate(this.props.item) will call it with the clicked item as the argument.
Sorry, it's this.props.onItemActive. Fixed.
cannot read property 'props' of undefined for the line this.props.onItemActive(this.props.item);. And, by the waym it doesn't change the elements' classes.
That's because I forgot that with ES6 classes, React no more autobind this to non react functions. Fixed. Added .bind(this) to onClick={this.setActive.bind(this)} and onItemActive={this.onItemActive.bind(this)}
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.