203

TLDR: Use defaultChecked instead of checked, working jsbin.

Trying to setup a simple checkbox that will cross out its label text when it is checked. For some reason handleChange is not getting fired when I use the component. Can anyone explain what I'm doing wrong?

var CrossoutCheckbox = React.createClass({
  getInitialState: function () {
    return {
        complete: (!!this.props.complete) || false
      };
  },
  handleChange: function(){
    console.log('handleChange', this.refs.complete.checked); // Never gets logged
    this.setState({
      complete: this.refs.complete.checked
    });
  },
  render: function(){
    var labelStyle={
      'text-decoration': this.state.complete?'line-through':''
    };
    return (
      <span>
        <label style={labelStyle}>
          <input
            type="checkbox"
            checked={this.state.complete}
            ref="complete"
            onChange={this.handleChange}
          />
          {this.props.text}
        </label>
      </span>
    );
  }
});

Usage:

React.renderComponent(CrossoutCheckbox({text: "Text Text", complete: false}), mountNode);

Solution:

Using checked doesn't let the underlying value change (apparently) and thus doesn't call the onChange handler. Switching to defaultChecked seems to fix this:

var CrossoutCheckbox = React.createClass({
  getInitialState: function () {
    return {
        complete: (!!this.props.complete) || false
      };
  },
  handleChange: function(){
    this.setState({
      complete: !this.state.complete
    });
  },
  render: function(){
    var labelStyle={
      'text-decoration': this.state.complete?'line-through':''
    };
    return (
      <span>
        <label style={labelStyle}>
          <input
            type="checkbox"
            defaultChecked={this.state.complete}
            ref="complete"
            onChange={this.handleChange}
          />
          {this.props.text}
        </label>
      </span>
    );
  }
});
Edgar
  • 6,022
  • 8
  • 33
  • 66
jdarling
  • 2,438
  • 2
  • 14
  • 10
  • 4
    First off, why not add an onChange that just does `this.setState({checked: !this.state.checked})` Easier than having to store a value. Then a ternary operator in the checked attrubute: `checked={this.state.checked ? 'checked': null}` – zackify Oct 28 '14 at 18:34
  • That's how it started out, but it never seemed to update. So I started fluffing it out here and there to debug what wasn't getting fired. Ideally will go back to the simplest form when complete :) – jdarling Oct 28 '14 at 18:37
  • 2
    Assuming your mountNode is an actual dom node, you would have to use `this.refs.complete.getDOMNode().checked`. see fiddle http://jsfiddle.net/d10xyqu1/ – trekforever Oct 28 '14 at 18:42
  • He can just use state instead of getting the dom node though: http://jsfiddle.net/d10xyqu1/1/ It works fine, you must have mistyped something. – zackify Oct 28 '14 at 18:45
  • 4
    Ignore the TLDR comment - defaultChecked isn't always the answer – Chris Apr 13 '16 at 14:10

8 Answers8

299

To get the checked state of your checkbox the path would be:

this.refs.complete.state.checked

The alternative is to get it from the event passed into the handleChange method:

event.target.checked
Pierre Arnaud
  • 10,212
  • 11
  • 77
  • 108
zbyte
  • 3,705
  • 1
  • 17
  • 13
  • 5
    handleChange is never getting called, doesn't matter if you click the checkbox or the label, handleChange doesn't get called :(. – jdarling Oct 28 '14 at 18:38
  • 16
    Try using defaultChecked={this.state.complete} instead of "checked" on your input. – zbyte Oct 28 '14 at 18:40
  • That was it... Searched forever looking and poking around. Will update question with complete working answer in case others run across this too. – jdarling Oct 28 '14 at 18:45
  • But why - having the same problem but you're supposed to use `checked` for controlled components :/ – Dominic May 23 '16 at 08:58
  • 6
    setting `checked` means the state is managed outside the component. When the user clicks there is nothing to call `handleChange` as no state is updated. Instead you would need to listen for `onClick` and trigger a state update there. – zbyte Jun 06 '16 at 22:50
53

It's better not to use refs in such cases. Use:

<input
    type="checkbox"
    checked={this.state.active}
    onClick={this.handleClick}
/>

There are some options:

checked vs defaultChecked

The former would respond to both state changes and clicks. The latter would ignore state changes.

onClick vs onChange

The former would always trigger on clicks. The latter would not trigger on clicks if checked attribute is present on input element.

Lin
  • 1,041
  • 10
  • 6
  • 9
    I'm using React 16.13.1 and you cannot provide a checked property without an onChange property. If I define both, and make the checked property responsive to inputs, then I get the desired behavior and onChange triggers every time I click on the box. So I think this answer has become outdated. – Ian Sep 15 '20 at 16:42
  • This idea save my life of debugging – Mickey Oct 30 '22 at 13:39
24

If you have a handleChange function that looks like this:

handleChange = (e) => {
  this.setState({
    [e.target.name]: e.target.value,
  });
}

You can create a custom onChange function so that it acts like an text input would:

<input
  type="checkbox"
  name="check"
  checked={this.state.check}
  onChange={(e) => {
    this.handleChange({
      target: {
        name: e.target.name,
        value: e.target.checked,
      },
    });
  }}
/>
spencer.sm
  • 19,173
  • 10
  • 77
  • 88
  • isn't `handleChange` on `input` should be `this.handleChange`? – Ardhi Dec 17 '18 at 04:44
  • you made a little mistake -> [e.target.name]: e.target.checked – Haikel Nov 20 '20 at 19:29
  • 4
    @Haikel if you had a handleChange function that was only handling the checkbox input that'd be true, but if you notice in the second code block we're calling `handleChange` in an arrow function and setting `target.value` to `e.target.checked` (essentially creating a faux event). It was done this way so that the same handleChange function could work for text inputs as well since that's how they pass their value in change events. – spencer.sm Nov 20 '20 at 19:44
15

In the scenario you would NOT like to use the onChange handler on the input DOM, you can use the onClick property as an alternative. The defaultChecked, the condition may leave a fixed state for v16 IINM.

 class CrossOutCheckbox extends Component {
      constructor(init){
          super(init);
          this.handleChange = this.handleChange.bind(this);
      }
      handleChange({target}){
          if (target.checked){
             target.removeAttribute('checked');
             target.parentNode.style.textDecoration = "";
          } else {
             target.setAttribute('checked', true);
             target.parentNode.style.textDecoration = "line-through";
          }
      }
      render(){
         return (
            <span>
              <label style={{textDecoration: this.props.complete?"line-through":""}}>
                 <input type="checkbox"
                        onClick={this.handleChange}
                        defaultChecked={this.props.complete}
                  />
              </label>
                {this.props.text}
            </span>
        )
    }
 }

I hope this helps someone in the future.

akiespenc
  • 305
  • 3
  • 8
13

In case someone is looking for a universal event handler the following code can be used more or less (assuming that name property is set for every input):

    this.handleInputChange = (e) => {
        item[e.target.name] = e.target.type === "checkbox" ? e.target.checked : e.target.value;
    }
Pawel Gorczynski
  • 1,227
  • 1
  • 15
  • 17
3

onChange will not call handleChange on mobile when using defaultChecked. As an alternative you can can use onClick and onTouchEnd.

<input onClick={this.handleChange} onTouchEnd={this.handleChange} type="checkbox" defaultChecked={!!this.state.complete} />;
Mo.
  • 26,306
  • 36
  • 159
  • 225
tanner burton
  • 1,049
  • 13
  • 14
1

In material ui, state of checkbox can be fetched as

this.refs.complete.state.switched
Sakshi Nagpal
  • 1,003
  • 7
  • 16
0

For anyone encountering this questions in the future. My problem was referencing the input name instead of id in the htmlFor of the label.

So changing this:

<input type="checkbox" name="test" value="isGood" onChange={() => console.log('called')} />
<label htmlFor="test" >Is Good?</label>

to this

<input type="checkbox" name="test" id="test" value="isGood" onChange={() => console.log('called')} />
<label htmlFor="test" >goood</label>

Fixed the issue

HosseinAgha
  • 759
  • 6
  • 18