React checkbox doesn't toggle

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)


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.