1

I'm trying to update states based on other states after rendering the jsx.

Some of the states are updated but some of them are not updated.
Please consider checking 'ComponentDidMount()'. I'm not sure what is going on!
Why are they not getting updated accordingly?
I'm confused!

import React, { Component } from "react";

export class MathQuiz extends Component {
  constructor(props) {
    super(props);
    this.state = {
      num1: 0,
      num2: 0,
      op_type: "",
      op: "",
      result: 0,
      no_right: 0,
      no_wrong: 0,
      options: <li />,
      ans_pos: 0,
      options_and_pos: [[], 0]
    };
  }

  componentDidMount() {
    this.genNums();
    this.initQuiz(this.props.location.state.op_type);
  }

  initQuiz(op_type) {
    this.setState({ op_type: op_type });
    if (op_type === "Addition") {
      this.setState({ op: "+" });
      this.setState(prevState => ({ result: prevState.num1 + prevState.num2 }));
    } /* Code */
    } else if (op_type === "Multiplication") {
      this.setState({ op: "x" });
      this.setState(prevState => ({ result: prevState.num1 * prevState.num2 }));
    console.log(this.state.result);
    this.setState({ options_and_pos: this.getOptions(this.state.result) });
    this.setState({
      options: this.state.options_and_pos[0].map((ele, i) => (
        <li key={i}>{ele}</li>
      ))
    });
    this.setState({ ans_pos: this.state.options_and_pos[1] });
  }
  genNums() {
    this.setState({
      num1: this.genRandRange(1, 100),
      num2: this.genRandRange(1, 100)
    });
  }
  getOptions(ans) {
    /* Code */
    return [ans_options, rand_pos];
  }
  render() {
    return (
      <div className="math_quiz_box">
        /* JSX Code */
      </div>
    );
  }
}
Suraj Singh
  • 11
  • 1
  • 2

2 Answers2

3

React docs helps you:

setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall.

And also gives you the solution:

Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied.

So, ensure that you don't use the state, right after updating the state.

Joey
  • 351
  • 1
  • 11
3

It is worse that state changed by other state. It is better state changed by props, or event handler.

For example, this.genNums() is not necessary execute in componentDidMount()

this.setState(prevState => ({ result: prevState.num1 + prevState.num2 }));

code above could be altered as:

this.setState({result: 2*this.genRandRange(1, 100)}) //assume this.genRandRange() return Number not String

And the whole paragraph could be altered:

import React, { Component } from "react";

export class MathQuiz extends Component {
  constructor(props) {
    super(props);
    this.state = {
      //num1: 0,    if only used by other state
      //num2: 0,
      //op_type: "", if only used by other state
      op: "",
      result: 0,
      no_right: 0,
      no_wrong: 0,
      options: <li />,
      ans_pos: 0,
      //options_and_pos: [[], 0]  if only used by other state
    };
  }

  componentDidMount() {
    this.initQuiz(this.props.location.state.op_type);
  }

  initQuiz(op_type) {
    if (op_type === "Addition") {
      this.setState({ 
         op:"+",
         result: 2*this.genRandRange(1, 100) 
      });
     /* Code */
    } 
    else if (op_type === "Multiplication") {
       this.setState({
          op:"x", 
          result: Math.pow(this.genRandRange(1, 100),2),
          options: this.getOptions(Math.pow(this.genRandRange(1, 100),2))[0].map((ele, 
                   i) => (<li key={i}>{ele}</li>)),
          ans_pos:this.getOptions(Math.pow(this.genRandRange(1, 100),2))[1]
        });
     }
  }
  genNums() {
    this.setState({
      num1: this.genRandRange(1, 100),
      num2: this.genRandRange(1, 100)
    });
  }
  getOptions(ans) {
     /* Code */
    return [ans_options, rand_pos];
  }
  render() {
    return (
      <div className="math_quiz_box">
        /* JSX Code */
      </div>
    );
  }
}

The code above, just do setState once, could prevent problem like infinte loop or state update chain.

If you have to do setState chain, use

this.setState({
    example:"example value"
},()=>{
    // when example value has been set, then execute follow statement.
    this.setState({
       example2:this.state.example+"2"  //example2:"example value2"
    })
})
Kuo-hsuan Hsu
  • 647
  • 1
  • 6
  • 12