34

When I click the checkbox, the check mark doesn't disappear although the console.log in the onChange handler indicates the state changed to false. On the other hand, when the state is changed by a separate button, the check mark properly toggles on and off.

export default class TestComponent extends Component {

constructor(props) {
    super(props);
    this.state = {
        is_checked: true
    };
    this.updateCheckbox = this.updateCheckbox.bind(this);
    this.pressButton = this.pressButton.bind(this);
}
updateCheckbox(event) {
    event.preventDefault();

    this.setState({is_checked: !this.state.is_checked});
    console.log(this.state.is_checked);  // This logs 'false' meaning the click did cause the state change 
    console.log(event.target.checked);  // However, this logs 'true' because the checkmark is still there 

}
pressButton(event){
    event.preventDefault();
    this.setState({is_checked: !this.state.is_checked});
}
render(){

    return (
   <input type="checkbox" onChange={this.updateCheckbox} checked={this.state.is_checked} ></input>
   <button  onClick={this.pressButton}>change checkbox state using button</button>
    );
}
}
1
  • I know it's been a while but the accepted answer is somehow misleading (not wrong though). Besides I think you shouldn't solve this issue with two-way binding, as you did in your own answer. Commented Aug 30, 2016 at 11:11

4 Answers 4

41

I think I see what's happening.

You click the button, and it toggles is_checked, which either checks or unchecks the box. But that ends up triggering an onChange for the checkbox, which also toggles the state... You've actually coded an infinite loop. Although, since React batches/debounces setState operations, your code won't lock your page up.

Try this:

2019 Update for hooks API:

import React, { useState } from 'react';

const Component = () => {
  const [isChecked, setIsChecked] = useState(true);

  return (
    <div>
      <input
        type="checkbox"
        onChange={(event) => setIsChecked(event.currentTarget.checked)}
        checked={isChecked}
      />
      <button onClick={() => setIsChecked(!isChecked)}>
        change checkbox state using this button
      </button>
    </div>
  );
};

Original:

var React = require("react");

var Component = React.createClass({
    getInitialState: function() {
        return {
            isChecked: true
        };
    },

    handleCheckboxChange: function(event) {
        console.log("checkbox changed!", event);
        this.setState({isChecked: event.target.checked});
    },

    toggleIsChecked: function() {
        console.log("toggling isChecked value!");
        this.setState({isChecked: !this.state.isChecked});
    },

    handleButtonClick: function(event) {
        console.log("button was pressed!", event);
        this.toggleIsChecked();
    },

    render: function() {
        return (
            <div>
                <input type="checkbox" onChange={this.handleCheckboxChange} checked={this.state.isChecked} />
                <button onClick={this.handleButtonClick}>change checkbox state using this button</button>
            </div>
        );
    }
});

module.exports = Component;

Note that you can make this code even cleaner by using React's valueLink (read more here: https://facebook.github.io/react/docs/two-way-binding-helpers.html)

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

5 Comments

Thanks for your help. The code you provided had the same issue, but the link for two way binding was helpful. Updated my post to show the working solution.
I forgot to add the line inside of handleCheckboxChange where it actually updates the isChecked state - the secret is using the value of event.target.checked. I updated the code, just for reference. Here's a fiddle too: jsfiddle.net/xqb2mxsq
You can also fix this by just removing the "event.preventDefault();" line. Since that prevents the checkbox from being checked. thus it will always be the same state.
I know this is old but the root of the issue is e.preventDefault(). By preventing the default behavior of the on chagne, you're preventing the change from happening even though the event handler is invoked. So in other words, the checkbox changes, the state change happens, and then the checkbox unsets itself since its behavior was prevented. So, the initial code will work by simply removing e.preventDefault()
This worked for me with useReducer as well. Thanks for this.
19

The reason for this behavior has to do with an implementation detail of React - more specifically the way how React normalizes change handling cross browser. With radio and checkbox inputs React uses a click event in place of a change event. When you apply preventDefault within an attached event handler, this stops the browser from visually updating the radio/checkbox input. There are two possible workarounds:

  • use stopPropagation alternatively
  • put the toggle into setTimeout: setTimeout(x => this.setState(x), 0, {is_checked: !this.state.is_checked});

Preferably you don't use preventDefault at all, unless it is absolutely necessary.

Look into this React Github issue for further information.

4 Comments

getting rid of preventDefualt fixed the issue for me, using react 15 and typescript 2.2 ( sorry for bad formatting) toggleMyCheckbox(checkboxExent: React.ChangeEvent<HTMLInputElement>) { checkboxExent.preventDefault(); this.setState({ checkboxModel: this.state.checkboxModel, checked: !this.state.checked }) } to.. toggleMyCheckbox(checkboxExent: React.ChangeEvent<HTMLInputElement>) { this.setState({ checkboxModel: this.state.checkboxModel, checked: !this.state.checked }) }
Thank You I got rid of the prevent default also and it worked for me.
Great job explaining. Really helped out this React newbie.
Thank you, preventDefault() was my issue as well.
1

Keep in mind that setState is asynchronous, so:

 console.log(event.target.checked);

shall not reflect the changes immediately. My way to handle several checkbox fields:

 toggleCheckbox(name, event) {
    let obj = {}; 
    obj[name] = !this.state[name];
    this.setState(obj);
 }

The fields:

<input type="checkbox" name="active" value={this.state.active} checked={this.state.active} onChange={this.toggleCheckbox.bind(this, 'active')} />

<input type="checkbox" name="qtype" value={this.state.qtype} checked={this.state.qtype} onChange={this.toggleCheckbox.bind(this, 'qtype')} />

Comments

0

Changed the code per the React documentation on Two way binding which Kabir provided the link for.

To make it work I had to

  1. use "React.createClass" instead of "extends Component" in order to use the LinkedStateMixin

    import react from 'react/addons'
    
    var TestComponent = React.createClass({
    mixins: [React.addons.LinkedStateMixin],
    render: function(){ ....
    
  2. remove onChange='updateCheckbox' and use this.linkState instead.

    <input type="checkbox" checkedLink={this.linkState(this.state.is_checked)} ></input> 
    

Comments

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.