0

I've been working on a BMI calculator written in react.js class component, i found out that clicking on a submit button won't mutate some of the this.state values at first time, but the second click would mutate all values, i've read relevant questions in these links:

Pass values to child component on click submit button - ReactJS, click twice

ReactJS - Need to click twice to set State and run function

but i couldn't figure it out how to add changes to my code. Here is my code:

import React, {Component} from 'react';
class App extends Component{
    constructor(props){
        super(props);
        this.state = {fullName: '', weight: '', height: '', bmi: '', message: '', optimalWeight: ''};
        this.handleChange = this.handleChange.bind(this);
        this.calculateBMI = this.calculateBMI.bind(this);
        this.handleSubmit =this.handleSubmit.bind(this);
    }

handleChange(e){
    this.setState({[e.target.name]: e.target.value});
}

calculateBMI(){
    let heightSquared = (this.state.height/100 * this.state.height/100);
    let bmi = this.state.weight / heightSquared;
    let low = Math.round(18.5 * heightSquared);
    let high = Math.round(24.99 * heightSquared);
    let message = "";
    if(bmi >= 18.5 && bmi <= 24.99){
        message = "You are in a healthy weight range";
    }
    else if(bmi >= 25 && bmi <= 29.9){
        message = "You are overweight";
    }
    else if(bmi >= 30){
        message="You are obese";
    }
    else if(bmi < 18.5){
        message="You are under weight";
    }
    this.setState({message: message});
    this.setState({optimalWeight: "Your suggested weight range is between "+low+ " - "+high});
    this.setState({bmi: Math.round(bmi * 100) / 100});
}


handleSubmit(e){
    this.calculateBMI();
    e.preventDefault();
    console.log(this.state);

}

    render(){
        return(
            <div className="App">
                <div className="App-header">
                    <h2>BMI calculator</h2>
                </div>
                <form onSubmit={this.handleSubmit}>
                    <label>
                        Please enter your name
                    </label>
                    <input type="text" name="fullName" value={this.state.fullName} onChange={this.handleChange} />
                    <label>
                        Enter your height in cm
                    </label>
                    <input type="number" name="height" value={this.state.height} onChange={this.handleChange} />
                    <label>
                        Enter your weight in kg
                    </label>
                    <input type="number" name="weight" value={this.state.weight} onChange={this.handleChange} />
                    <input type="submit" value="Submit"/>
                </form>
            </div>
        );
    }
}

export default App;

update:

I also check the question Using async setState but my problem is not about updating in an async way but thanks to @LMulvey i did solve it by grouping setState and adding a console.log as callback function to it in calculateBMI()

  • You can set all the state properties you want in one `setState` call. – Emile Bergeron Apr 10 '19 at 17:25
  • Also, setState is async, explaining why your console log is not up to date. – Emile Bergeron Apr 10 '19 at 17:26
  • Possible duplicate of [Using async setState](https://stackoverflow.com/questions/43370176/using-async-setstate) – Emile Bergeron Apr 10 '19 at 17:26
  • @EmileBergeron i did group **setState** in **calculateBMI()** but the main solution to my problem is to add **console.log** to the last **setState** in **calculateBMI()** as @ LMulvey answered in below – Jordan Picaso Apr 10 '19 at 18:07
  • My first comment was just a code review tip. The solution was in my second comment, which is already answered in my third comment. – Emile Bergeron Apr 10 '19 at 18:09
  • Seeing your edit, it looks like you missed the point of the duplicate candidate. Even if you don't want to "update", `setState` is still async and the callback solution explained in the [_Using async setState_](https://stackoverflow.com/questions/43370176/using-async-setstate) is the same concept needed here. This is a common React mistake. – Emile Bergeron Apr 10 '19 at 18:20
  • @EmileBergeron i saw your third comment but i couldn't solve my problem through that in https://stackoverflow.com/questions/43370176/using-async-setstate , on the other hand my code is a simple react class component and not using redux – Jordan Picaso Apr 10 '19 at 18:43
  • While the other asker is using a redux action function instead of `console.log`, the problem and solution is the same... – Emile Bergeron Apr 10 '19 at 18:56

1 Answers1

2

Your app is actually working just fine. The problem is that you're trying to log your state before setState has fully finished. If you instead attach your console.log as a callback to your final setState call, you'll see that it's working as intended.

this.setState({bmi: Math.round(bmi * 100) / 100}, () => console.log(this.state));

It's worth noting that you don't need one setState call for each property that you're updating and you can group them together like this, which would also be leading to your issue of race conditions here:

 this.setState({
    bmi: Math.round(bmi * 100) / 100,
    optimalWeight: "Your suggested weight range is between "+low+ " - "+high,
    message
}, () => console.log(this.state));
LMulvey
  • 1,662
  • 8
  • 14
  • 1
    thanks , grouping **setState** and attaching **console.log** as a callback to final **setState** is awesome idea and it works fine – Jordan Picaso Apr 10 '19 at 17:41