15

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?

exchez
  • 493
  • 1
  • 4
  • 13
  • Any chances that your `if (this.state.foods[selectedFood])` is not valid for some reason ? maybe `this.state.foods` is still empty the first time or something like that ? – Mouhamed Halloul Jun 15 '16 at 23:15
  • What's in your `getInitialState` function? Can you post the entire component, perhaps in a JSFiddle? – Austin Pocus Jun 15 '16 at 23:39
  • thanks, i added everything up to the render method – exchez Jun 16 '16 at 00:36
  • @MouhamedHalloul it is passing the conditional b/c the other code inside it executes – exchez Jun 16 '16 at 00:41
  • 1
    I was able to get it to work by replacing it with this: `var state = Object.assign(this.state, {selectedFoods: newSelections}); this.setState(state);` – exchez Jun 16 '16 at 01:08
  • 1
    Does this answer your question? [setState doesn't update the state immediately](https://stackoverflow.com/questions/41278385/setstate-doesnt-update-the-state-immediately) – ggorlen Jul 05 '21 at 17:12

4 Answers4

47

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);
});
Michael Parker
  • 12,724
  • 5
  • 37
  • 58
  • 2
    this is really good to know. my component's state isn't getting updated also by evidence of the DOM not changing as intended though. – exchez Jun 16 '16 at 00:38
  • Just to note: I was struggling with a toggling function in which I needed something like this: "Immediately after this.setState i needed a function to trigger. I solved it by passing that function call within the setState callback as @Michael said and it worked fantastically. – Giancarlo Manuel Guerra Salvá Nov 26 '16 at 15:55
  • Thanks for this! I was having a headache of a time trying to figure this out. Now all of my setState uses a callback and I have no issues. – Timmo Apr 06 '17 at 13:50
  • Excelent answer. it helped my code; Now I can make a request as soon the setState has been set. :) – Eddwin Paz Oct 15 '18 at 15:58
  • Thanks alot for this. – ThivankaW Oct 30 '21 at 22:44
2

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.

sibumi
  • 81
  • 4
0

I also suffered from this problem for a day and got the solution. I just added in myCode the componentDidUpdate() method , and now everything working well. For the flow of the life cycle check out the life cycle image given in link.

Pravin Ghorle
  • 606
  • 7
  • 7
0

In my problem I was making an API request and then setting the state with the returned values. I did not want the page to re-render so my state values where batching when updated and updated after 2-3 tries.

I fixed it when I noticed my post request was executing (sending data) during OPTIONS and then my GET request would retrieve data, then my POST would fulfill. If someone experiences the problem with API calls and reloading the page to update state is not an option maybe this will help.

setData(newEquipment).then(() => {
            grabData(nS).then(json => {
                if (json["cs"]) {
                    json["cs"].map((item: CMs) => {
                        if (item.label === "SE") {
                            item.metrics.map((mes: Ms) => {
                                if (mes.label === "1") {
                                    const a = mes.value;
                                    setSEs(a);
                                }
                            })
                        }
                    }
                    )
                }
            });
        });