Sean Magyar
Sean Magyar

Reputation: 2380

reactjs setState doesn't change properly

I have a small react app. When selected dep/arr station changes it refetches the schedule data from the BART API.

For some reason with the old code (see below) it didn't work properly. I did setState first then tried to use the new value of the depStation, but it showed the previous value. So for instance let's say I reload the page the initial depState value is SFIA. If I change the value to FRMT then the console.log(selected) shows FRMT but the console.log(this.state.depStation) still shows SFIA. If I change it again to HAYW the console.log(selected) shows HAYW but the console.log(this.state.depStation) shows FRMT. To make the app work I simply just used the selected instead of the this.state.depStation, but I guess this is not the best approach.

So I don't really understand why this.state.depStation shows the prev data after calling this.setState({depStation: selected}). Could sby explain me why this is happening?

Old version which was not working:

reRunFetching(selected, type) {
  if (type === "depart") {
    this.setState({depStation: selected});
    console.log(selected); //Showing properly what I select
    console.log(this.state.depStation); //For some reason the previously selected
  }
  else if (type === "arrive") {
    this.setState({arrStation: selected});
  }
  this.fetchingAPI(this.state.depStation, this.state.arrStation)
}

New version. This is working fine, but I guess it's not the best solution:

reRunFetching(selected, type) {
  if (type === "depart") {
    this.fetchingAPI(selected, this.state.arrStation)
    this.setState({depStation: selected});
    console.log(selected, this.state.arrStation);
  }
  else if (type === "arrive") {
    this.fetchingAPI(this.state.depStation, selected)
    this.setState({arrStation: selected});
    console.log(this.state.depStation, selected);
  }
}

rest of index.js

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      schedules: [],
      depStation: DEP_STATION,
      arrStation: ARR_STATION
    };

    this.fetchingAPI(this.state.depStation, this.state.arrStation)
  }

  fetchingAPI(departureStation, arrivalStation) {
    fetch("http://api.bart.gov/api/sched.aspx?cmd=depart&orig=" + departureStation + "&dest=" + arrivalStation + "&date=now&key=MW9S-E7SL-26DU-VV8V&b=0&a=4&l=0")
    .then(function(response) {
        return response.text();})
    .then((responseXML) => {
      let tripsArray = this.parsingXML(responseXML);
      this.setState({schedules: tripsArray});
    })
    .catch((error) => {
      console.log(error);
    });
  }

  render () {
    return (
      <div>
        <div className="row">
          <div className="col-md-5 col-md-offset-1 search-bar">
            <SelectDepart onSelectedChange={selected => this.reRunFetching(selected, "depart")}/>
          </div>
          <div className="col-md-5 search-bar">
            <SelectArrive onSelectedChange={selected => this.reRunFetching(selected, "arrive")}/>
          </div>
        </div>
        <TimeTable schedules={this.state.schedules} />
      </div>
    )
  }

Upvotes: 0

Views: 705

Answers (2)

Manish
Manish

Reputation: 170

setState() is an asynchronous method which queue your state. so in order to access state value immidiately after setState you need to call a callback function as second argument to setState method, which will first set your state and after that will re render your view with updated state. below example will help you.

this.setState({
   depStation: selected
}, () => {
  // here you will get your depStation state value
  console.log(this.state.depStation,"depStation value")
});

Upvotes: 0

Ilya_S
Ilya_S

Reputation: 204

setState() is an asynchronous non-blocking method which doesn't immediately set the new state, as you expect it to. As the official docs says:

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.
There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.

If you need you can pass a callback a a second argument to setState() and it will be fired on state change:

this.setState({depStation: selected}, function() {
// some code
});

Upvotes: 1

Related Questions