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

4

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?

Propman answered 15/6, 2016 at 22:42 Comment(6)
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 ?Diaeresis
What's in your getInitialState function? Can you post the entire component, perhaps in a JSFiddle?Grapefruit
thanks, i added everything up to the render methodPropman
@MouhamedHalloul it is passing the conditional b/c the other code inside it executesPropman
I was able to get it to work by replacing it with this: var state = Object.assign(this.state, {selectedFoods: newSelections}); this.setState(state);Propman
Does this answer your question? setState doesn't update the state immediatelySunbathe
L
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);
});
Lukelukens answered 15/6, 2016 at 23:56 Comment(5)
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.Propman
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.Alphabetical
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.Sheridan
Excelent answer. it helped my code; Now I can make a request as soon the setState has been set. :)Placard
Thanks alot for this.Lie
S
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.

Stereochromy answered 13/10, 2020 at 15:30 Comment(0)
M
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.

Maugre answered 24/10, 2019 at 9:3 Comment(0)
M
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);
                                }
                            })
                        }
                    }
                    )
                }
            });
        });
Midpoint answered 11/5, 2022 at 0:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.