2

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>
  );
}
6
  • did you put a console.log() in the toggleClass() function to check whether it gets called? Commented Oct 30, 2017 at 12:44
  • yes, it didn't show up Commented Oct 30, 2017 at 12:45
  • Does TabLabel accept an onClick property? Does it do something with it? Commented Oct 30, 2017 at 12:45
  • In your case onClick will be just the part of props, you need to use it inside TabLabel component. Commented Oct 30, 2017 at 12:47
  • @rednaw No, the TabLabel Property doesn't do anything with it. I want to call the function toggleClass() in the <Sidebar /> Component. Commented Oct 30, 2017 at 12:48

2 Answers 2

1

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>

Sign up to request clarification or add additional context in comments.

5 Comments

Thank you very much vor your answer, it was very helpful. The only thing, which is not exactly clear to me is why you need a key property?
It's because I used an array and therefore each item within map needs a key prop. With that React knows which components have been selected, added, deleted or even modified as described here.
@Dario Also I added a key prop to tabs so you don't need to replicate for example Code and this.props.text.toLowerCase() every time you have to render TabLabel. onClick must know which tab has been clicked and your code only sets code as active tab.
@Dario I've just updated my answer so it matches your needs. Feel free to change it as you wish :)
@mersacarlin Thank you very much for your help. =)
1

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>
  );
}

2 Comments

But then I have to put the state in the TabLabel Component, right?
No, you don't need state in TabLabel. What is the problem you have?

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.