nCardot
nCardot

Reputation: 6587

Why is a function call without an argument in setState called before setState?

As was pointed out to me in another question, if I have a function call without an argument in setState, randomQuoteIndex(), and the function uses a state set in that setState, it's called before setState.

  componentDidMount() {
        fetch('https://gist.githubusercontent.com/nataliecardot/0ca0878d2f0c4210e2ed87a5f6947ec7/raw/1802a693d02ea086817e46a42413c0df4c077e3b/quotes.json')
          .then(response => response.json())
          .then(quotes => this.setState({
              quotes,
              randomQuoteIndex: this.randomQuoteIndex(),
              isDoneFetching: true
            }));
  }

randomQuoteIndex() {
    return random(0, this.state.quotes.length - 1);
  }

This results in an error because the state of quotes isn't available at the time of randomQuoteIndex() call.

However, if I change randomQuoteIndex() to instead use a passed in quotes parameter, as below, it works.

componentDidMount() {
    fetch('https://gist.githubusercontent.com/nataliecardot/0ca0878d2f0c4210e2ed87a5f6947ec7/raw/1802a693d02ea086817e46a42413c0df4c077e3b/quotes.json')
      .then(response => response.json())
      .then(quotes => this.setState({
          quotes,
          randomQuoteIndex: this.randomQuoteIndex(quotes),
          isDoneFetching: true
        }));
  }

  randomQuoteIndex(quotes) {
    return random(0, quotes.length - 1);
  }

This is not what I expected; I had assumed that the state of quotes would be available at the time randomQuoteIndex() is called since it was called within setState. Why is randomQuoteIndex() called before setState even though it's inside it?

Upvotes: 0

Views: 1054

Answers (2)

Aseem Upadhyay
Aseem Upadhyay

Reputation: 4537

It is because setState is an async operation. You can still use your original function by using setState as a callback

componentDidMount() {
        fetch('https://gist.githubusercontent.com/nataliecardot/0ca0878d2f0c4210e2ed87a5f6947ec7/raw/1802a693d02ea086817e46a42413c0df4c077e3b/quotes.json')
          .then(response => response.json())
          .then(quotes => this.setState({
              quotes,
              isDoneFetching: true
            }, () => {
             this.setState({
             randomQuoteIndex: this.randomQuoteIndex(),
        }); //this callback will be executed after your state is updated.
    }));

}

randomQuoteIndex() {
return random(0, this.state.quotes.length - 1);
}

So, essentially once a setState function is performed, the updated values will only reflect after the update lifecycle is finished, which means you can get the values either in componentDidUpdate method or by using the setState function as a callback.

I wouldn't recommend this solution but in this case, the function randomQuoteIndex() will get the updated state values without having any parameter passed.

Upvotes: 2

Ion
Ion

Reputation: 1292

Thats because at the time you passed the object to setState, you are executing inline call to this.randomQuoteIndex.

state.quotes did not exist yet in the state.

constructor() {
    this.state = { quotes: [] };

}

componentDidMount() {
    fetch('https://gist.githubusercontent.com/nataliecardot/0ca0878d2f0c4210e2ed87a5f6947ec7/raw/1802a693d02ea086817e46a42413c0df4c077e3b/quotes.json')
      .then(response => response.json())
      .then(quotes => {

          this.setState({
            quotes,
            randomQuoteIndex: this.randomQuoteIndex(),
            isDoneFetching: true
        })
      });

  }

  randomQuoteIndex() {
    return random(0, this.state.quotes.length - 1);
  }

Upvotes: 1

Related Questions