Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React setState not working on first try, but works on second?

I have the handleSelection method called when a button is clicked, however, if I click the button once the state does not get set when it gets to this.setState({selectedFoods: newSelections});. Everything else in the method executes correctly (as my various console.logs tell me :) ). When the button is clicked a second time, everything in the method gets executed again and the setState works.

var Flavor = React.createClass({
  getInitialState: function() {
    return { foods: {}, selectedFoods: [], affinities: [] };
  },
        componentDidMount: function() {
            $.ajax({
              url: this.props.url,
              dataType: 'json',
              cache: false,
              success: function(data) {
                this.setState({foods: data});
              }.bind(this),
              error: function(xhr, status, err) {
                console.error(this.props.url, status, err.toString());
              }.bind(this)
            });
          },
  componentDidUpdate: function () {
    $('#select-food').selectize({
      onChange: this.handleSelection
      }
    );
  },
  handleSelection: function (e) {
    if (e.target) {
      var selectedFood = e.target.id;
    } else {
      var selectedFood = e;
    }
    console.log(selectedFood);

    if (this.state.foods[selectedFood]) {
      var selections = this.state.selectedFoods;
      var newSelections = selections.concat(selectedFood);
      console.log("newSelections: "+newSelections)
      var state = Object.assign(this.state, {selectedFoods: newSelections});
      this.setState(state);
      console.log("state: "+this.state.selectedFoods)
      this.handleAffinities();
    } else {
      console.log("** "+selectedFood+" **");
    }

  },
  handleAffinities: function() {

    console.log("selectedFoods: "+this.state.selectedFoods.length)
    if (this.state.selectedFoods.length > 0) {
      var allAffinities = this.state.selectedFoods.map((food) => {
        return this.state.foods[food];
      });
      console.log(allAffinities.length);
      console.log(allAffinities);

      var commonAffinities = allAffinities[0];

      allAffinities.forEach((affinities) => {
        commonAffinities = commonAffinities.filter((n) => {
          return affinities.indexOf(n) != -1;
        });
      })

      this.setState({affinities: commonAffinities});
    } else {
      this.setState({ affinities: [] });
    }

  },
  handleRemove: function(food) {
    var selectedFoods = this.state.selectedFoods;
    var index = selectedFoods.indexOf(food);
    var updatedSelection = selectedFoods.splice(index, 1);
    this.setState({selectedFoods: selectedFoods});
    this.handleAffinities();
  },

Why does everything execute correctly the first time except my setState function? And why it work on the second click?

like image 317
exchez Avatar asked Jun 15 '16 22:06

exchez


2 Answers

The state is changing exactly the way it is supposed to. The problem is that your console.log statements immediately after your call to setState are firing before the new state is set.

From the docs:

setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.

If you'd like to fire a console.log statement after the state transition completes, pass a function as a callback to setState().

this.setState({selectedFoods: newSelections}, () => {
    console.log(this.state.selectedFoods);
});
like image 180
Michael Parker Avatar answered Sep 18 '22 14:09

Michael Parker


As @Micheal stated, setState function stays pending, and first console.log works. Anybody who wants to see or experiment this situation, may have a look at my codesandbox.io example. Here, inside YesNoComponentWithClass, yes button logs empty for the first click, while no button logs the expected value.

like image 42
sibumi Avatar answered Sep 19 '22 14:09

sibumi