4

I know setState() does not immediately mutate this.state. So in the code below, checkbox is always unchecked after clicking the button. How to fix this?

handleClick = () => {
  this.setState({tick: !this.state.tick})
}

render() {
  return (
    <button type='button' onClick={() => this.handleClick()} >
      <input type="checkbox" value={this.state.tick} />
      Tick here
    </button>
  )
}
lucahuy
  • 790
  • 2
  • 9
  • 22

2 Answers2

6

use checked instead of value:

<input type="checkbox" checked={this.state.tick} />

from the spec:

checked: Boolean; if present, the checkbox is currently toggled on

value: The string to use as the value of the checkbox when submitting the form, if the checkbox is currently toggled on

Community
  • 1
  • 1
ic3b3rg
  • 14,629
  • 4
  • 30
  • 53
1

ic3b3rg's answer highlights what needs to be changed in the code for the checkbox to work. I'm going to highlight a few other things that could be improved.

  1. Checkbox check state should be controlled with checked attribute

  2. Don't declare your event handlers with arrow functions as it will create a new anonymous function during every single render. It's a good idea to bind a function to the class and pass it to the event handler.

Something like this

constructor(props) {
  super(props);
  this.handleClick = this.handleClick.bind(this);
}

handleClick() {
  ...
}

// render
<button type = 'button' onClick = {this.handleClick} >
  1. When you want to update state based on existing state value, it's usually not a good idea to call this.state.key directly in your setState function as setState is an async call and you can't exactly say what the value of your current state will be. Instead, if you use this.setState((prevState, props) => ({}) callback, your state value will be updated based on what your existing state value was during invocation.

Change this

this.setState({tick: !this.state.tick})

to

this.setState((prevState, props) => ({
  tick: !prevState.tick
}));

Here's a full working example

class Example extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      tick: false
    };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    // when updating state values from existing state values
    // you should use not use value: !this.state.value directly
    this.setState((prevState, props) => ({
      tick: !prevState.tick
    }));
  }

  render() {
    return (
      <button type = 'button' onClick={this.handleClick} >
        <input type = "checkbox" checked={this.state.tick} />Tick here
      </button>
    );
  }
}

ReactDOM.render( <
  Example / > ,
  document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="react"></div>
Dinesh Pandiyan
  • 5,814
  • 2
  • 30
  • 49
  • 1
    Per [documentation](https://reactjs.org/docs/react-component.html#setstate), it says `setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall`. Could you also explain when `this.state` fails? – lucahuy Dec 18 '18 at 04:38
  • @lucahuy I have edited the answer and included an explanation. – Dinesh Pandiyan Dec 18 '18 at 04:54
  • according to this [question](https://stackoverflow.com/questions/30782948/why-calling-react-setstate-method-doesnt-mutate-the-state-immediately), accessing `this.state` after calling `setState` can fail or pass. True? In my case, it passes, but there is no guarantee, is there? – lucahuy Dec 18 '18 at 05:20
  • `setState` will never fail. It might be delayed or batched which will eventually execute at one point of time. Once the `setState` execution is completed, render will be triggered. So at any point, what you see on your screen is the reflection of what your latest state is. There are no fail cases, but only delayed cases. – Dinesh Pandiyan Dec 18 '18 at 05:43
  • So if I call `checked={this.state.tick}` before `setState` completed, then `checked` won't change? There is no guarantee that `this.state` will give you the correct value after `setState`? – lucahuy Dec 18 '18 at 05:52
  • 2
    `checked={this.state.tick}` is inside your `render` method. You cannot invoke your render method. React does it automatically at different points (`componentDidMount`, etc). Every time a state update is complete, `render` will automatically be invoked. So your render will have the latest state value and `checked={this.state.tick}` will be based on latest `tick` value. – Dinesh Pandiyan Dec 18 '18 at 06:01
  • This is what I was asking. Your last comment explained why. In the other question, `console.log(this.state)` not in `render` method, so it could give wrong value. – lucahuy Dec 18 '18 at 06:12
  • it also could be in `componentDidUpdate` method as I understand React. Here we will have latest state isn't it? – Dima Hmel Jan 18 '20 at 17:14