Dario
Dario

Reputation: 640

React.js: Toggle className on click

I have a <Sidebar /> component which creates two <TabLabel /> components.

I want to toggle the className is-active, if you click a tab label the class is-active should be added to the <li> element and removed on the other one.

There is no error ocuring, but the function toggleClass() doesn't seem to get invoked.

<Sidebar /> Component:

constructor() {
  super();
  this.state = {
    active: 'generator'
  }
  this.toggleClass = this.toggleClass.bind(this);
}

toggleClass() {
  this.setState({active: 'code'});
}

render() {
  return (
    <div className="sidebar">
      <ul className="tabs-list">
        <TabLabel onClick={() => this.toggleClass()} active={this.state.active} text="Generator" />
        <TabLabel onClick={() => this.toggleClass()} active={this.state.active} text="Code" />
      </ul>
    </div>
  );
}

<TabLabel /> Component:

render() {
  return(
    <li className={this.props.text.toLowerCase() === this.props.active ? "is-active" : null}>
      <h2>{this.props.text}</h2>
    </li>
  );
}

Upvotes: 2

Views: 8735

Answers (2)

Hemerson Carlin
Hemerson Carlin

Reputation: 7424

Why don't you keep TabLabel as dumb as possible and only propagate props to it?

Also, TabLabel needs to dispatch click changes so Sidebar knows which Tab has been selected.

Testability gets easier and you can even improve TabLabel rendering on Sidebar (such as using an array of tabs).

class Sidebar extends React.Component {
  constructor() {
    super()

    this.state = {
      active: 'generator',
    }
  }

  toggleClass(tab) {
    this.setState({
      active: tab,
    })
  }

  render() {
    const { active } = this.state
    return (
      <div>
        <div className="sidebar">
          <ul className="tabs-list">
            <TabLabel
              onClick={this.toggleClass.bind(this, 'generator')}
              active={active === 'generator'}
              text="Generator"
            />
            
            <TabLabel
              onClick={this.toggleClass.bind(this, 'code')}
              active={active === 'code'}
              text="Code"
            />
          </ul>
        </div>
      </div>
    )
  }
}

const TabLabel = ({ active, text, onClick }) => 
  <li onClick={onClick} className={active ? 'is-active' : null}>
    <h2>{text}</h2>
  </li>

ReactDOM.render(
  <Sidebar />,
  document.getElementById('root')
)
ul {
  list-style-type: none;
  margin: 0;
  padding: 0;
}

li.is-active {
  background-color: red;
  color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<div id="root"></div>

Upvotes: 1

dfsq
dfsq

Reputation: 193271

The onclick event will not propagate from inside TabLabel to the onClick handler. You need to set dedicated event prop and invoke it in the TabLabel component:

render() {
  const isActive = this.props.text.toLowerCase() === this.props.active.toLowerCase()

  return(
    <li onClick={() => this.props.onSelect(this.props.text)} className={isActive ? "is-active" : null}>
      <h2>{this.props.text}</h2> 
    </li>
  );
}

And in Sidebar:

toggleClass(active) {
  this.setState({active});
}

render() {
  return (
    <div className="sidebar">
      <ul className="tabs-list">
        <TabLabel onSelect={this.toggleClass} active={this.state.active} text="Generator" />
        <TabLabel onSelect={this.toggleClass} active={this.state.active} text="Code" />
      </ul>
    </div>
  );
}

Upvotes: 1

Related Questions