Hantu
Hantu

Reputation: 194

When does the callback function in setState method execute?

In the following code, the callback of the setState method does not execute, as expected, after the state is set. If you check the console you will see what I mean. The state read in console.log is not the updated one. It's always the previous one (you can check this by clicking on the "Read array" button), but according to the react docs:

The second parameter to setState() is an optional callback function that will be executed once setState is completed and the component is re-rendered. Generally we recommend using componentDidUpdate() for such logic instead.

While I understand the recommendation is to use componentDidUpdate, I'm curious as to why it doesn't work as expected or if I'm reading the documentation wrong. By "once the setState is completed" I understand that it's executed once the new state has been saved.

class StateAsync extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
        items: [
        { text: "JavaScript" },
        { text: "React" },
        { text: "JSFiddle" },
        { text: "Awesome" }
      ],
      selectedItems: []
    }
    
    
    this.handleSelect = this.handleSelect.bind(this);
    this.test = this.test.bind(this);
  }
  
  handleSelect(e) {

        let selectedId = this.state.items[e.target.id].text;
        let clone = [...this.state.selectedItems];
        
        if(e.target.checked == false) {
          
            console.log("Removing item.");
            clone = clone.filter(item => item !== selectedId);
            this.setState({
                selectedItems: clone
            }, console.log("Remove callback result is: " + this.state.selectedItems)); //-- the callback does not execute AFTER the state is set
                       
        }else{
        
            this.setState({
                selectedItems: [...this.state.selectedItems, selectedId]
            }, console.log("Add callback result is: " + this.state.selectedItems)); //-- callback gets executed before the state is set
            console.log("Item added.");
        }
        
    }

    test(e) {
        e.preventDefault();

        console.log(this.state.selectedItems);
    }
  
  render() {
    return (
      <div>
        <span onClick={this.test} className="d-none d-md-block"><button className="btn btn-dim btn-outline-light">Read Array</button></span>
        <ol>
        {this.state.items.map((item,index) => (
          <li key={index}>
            <label>
              <input type="checkbox" onChange={this.handleSelect} id={index}/> 
              <span>{index} - {item.text}</span>
            </label>
          </li>
        ))}
        </ol>
        <span id="readout">
          
        </span>
      </div>
      
    )
  }
}

ReactDOM.render(<StateAsync />, document.querySelector("#app"))
body {
  background: #20262E;
  padding: 20px;
  font-family: Helvetica;
}

#app {
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  transition: all 0.2s;
}

li {
  margin: 8px 0;
}

h2 {
  font-weight: bold;
  margin-bottom: 15px;
}

.done {
  color: rgba(0, 0, 0, 0.3);
  text-decoration: line-through;
}

input {
  margin-right: 5px;
}
<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="app"></div>

Here's a codepen as well:

https://codepen.io/nicholas_cb/pen/VweXoVa?editors=0010

So, the question is does the callback in the setState method execute once the setState is completed or at the same time/before?

Upvotes: 0

Views: 376

Answers (3)

Steve Holgado
Steve Holgado

Reputation: 12071

You are executing the console log immediately and passing its return value (which will be undefined) as the callback.

This is why you get the current state logged and there is nothing (undefined as callback) to be executed later with the updated state.

Instead, you will need to pass a callback function that, in turn, calls the console log:

this.setState({
  selectedItems: clone
}, () => console.log("Remove callback result is: " + this.state.selectedItems)); 

Upvotes: 2

Dennis Vash
Dennis Vash

Reputation: 53884

The callback does execute after the state changes.

The problem that you didn't add a callback.

setState(updater, [callback])
this.setState(
  { selectedItems: clone },
  // closure on this.state.selectedItems
  () => console.log("Remove callback result is: " + this.state.selectedItems)
);

Upvotes: 1

Tushar
Tushar

Reputation: 675

That's because you are not passing a callback to the setState() method, instead you are directly passing the console.log() statement.

You need to do it this way:

this.setState({
     selectedItems: clone
}, () => {
     console.log("Remove callback result is: " + this.state.selectedItems
}); //-- the callback does not execute AFTER the state is set

Upvotes: 1

Related Questions