0

I'm trying to create a toggle button component with two buttons, where clicking on one disables the other and vice versa. I have a disabled state I know I have to toggle. But I'm not sure how to pan out the logic. Should there be one disabled state or two since there are two buttons?

  constructor(props) {
      super(props);
      this.state = { disabled: false }
    }

clicky(e) {
  //should dictate the toggle logic
}

render () {
  <div onClick={this.clicky.bind(this)}>
    <button disabled={this.state.disabled}>Item 1</button>
    <button disabled={this.state.disabled}>Item 2</button>
  </div>
}
1
  • I think you should have two disabled state , or you can just have one state to save which button is avaliable Commented Apr 24, 2018 at 5:57

4 Answers 4

1

Instead of keeping a boolean, you would keep the button identifier in the disabled state, which can scale upto any number of buttons instead of just two

constructor(props) {
    super(props);
    this.state = { disabled: "1" }
}

clicky(e) {
  //should dictate the toggle logic
  const id = e.target.id
  this.setState({ disabled: id })
}

render () {
  <div onClick={this.clicky.bind(this)}>
    <button disabled={this.state.disabled === "1"} id="1">Item 1</button>
    <button disabled={this.state.disabled === "2"} id="2">Item 2</button>
  </div>
}

Working demo

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

2 Comments

Using ids in react components isn't recommended as you can't reuse the component (or shouldn't anyways). A better method would be to use a data-prop, or a ref.
@AnilRedshift you can always reuse the component, you just to to change the id as you would change your data-props. In fact a button can become a component itself and get the id as a prop from the parent and use it
1

This should do:

constructor(props) {
  super(props);
  this.state = { button1Disabled: false }
}

clicky() {
  this.setState(prevState => ({
    button1Disabled: !prevState.button1Disabled
  }));
}

render () {
  return (
    <div onClick={this.clicky.bind(this)}>
      <button disabled={this.state.button1Disabled}>Item 1</button>
      <button disabled={!this.state.button1Disabled}>Item 2</button>
    </div>
  )
}

6 Comments

This doesn't work if you end up with clicking the button multiple times before the next paint comes through
@AnilRedshift, not true. His solution is perfectly fine. You won't ever get what you are describing with this solution.
@Dane, you did forget to negate the previous value though with ! in your setState.
@Chris: Consider that you mash button1 multiple times. In react, there is no guarantee that the render happens immediately, especially with upcoming fibers. So what happens is that you end up toggling the state back and forth, but what you really should be doing is disabling item 1, no matter how many times it's clicked
click event, click event, click event, click event.... this all batches and then asynchronously the clicky is called. The expected behavior should be that button 1 is disabled no matter how many times you click it
|
1

Dane has the best answer (in my opinion) so far, and my answer is quite similar to his. Though I do want to make some further suggestions:

  • First one would be to move the event listener out of your div and attach it to each of your buttons. The main reason being improving accessibility (e.g for screen readers) because static elements such as div, p, etc don't have a semantic meaning. Besides, it kind of makes more sense to have the actual button trigger something, rather than the buttons wrapper.

  • My second suggestion is to move your binding of your clicky() function into the constructor instead. This will prevent your component from binding on each re-render and will instead only do so once on component mount.

Here is a slighly modified version of Danes solution:

constructor(props) {
  super(props);
  this.state = { button1Disabled: false }
  this.clicky = this.clicky.bind(this);
}

clicky() {
  this.setState(prevState => ({
    button1Disabled: !prevState.button1Disabled
  }));
}

render () {
  return (
    <div>
      <button onClick={this.clicky} disabled={this.state.button1Disabled}>Item 1</button>
      <button onClick={this.clicky} disabled={!this.state.button1Disabled}>Item 2</button>
    </div>
  )
}

Comments

0

Since the buttons are inversely related, one state is a better idea, because with two variables, they can get out of sync. Also, because only one button is enabled at a time, managing state is simple, you just need to toggle the state every time a button is clicked:

constructor(props) {
     super(props);
     this.item1 = React.createRef();
     this.item2 = React.createRef();
     this.state = { enableFirst: true }
   }

clicky(e) {
  const enableFirst = e.target !== item1;
  this.setState({ enableFirst });
}

render () {
 <div onClick={this.clicky.bind(this)}>
   <button ref={item1} disabled={this.state.enableFirst}>Item 1</button>
   <button ref={item2} disabled={!this.state.enableFirst}>Item 2</button>
 </div>
}

1 Comment

How would they get out of sync?

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.