1

I'm trying build my first calculator in React. There seems to be a problem with my buttons (I think). While testing with console.logs, I discovered my first click of the addition button does NOT update all the setStates. Instead, the second click updates it correctly.

ANY idea why it isn't working?

import React, { Component } from 'react';


class calculator extends Component {
    state = { 
        xxxxx:0,
        v1:0,
        v2:0,
        isCal:null

     }

    makeAdd = () => {

        const firstNum = this.state.xxxxx;

        this.setState({
            v1:firstNum,
            xxxxx:0,
            isCal:"add"
        });

        console.log("Add, firstNum is " + firstNum );
        console.log("and v1 is: " + this.state.v1);
        console.log("and isCal is: " + this.state.isCal);

        document.getElementById('DaInserting').innerHTML = null;

    };


    okInsert = (num) => {
        let ss = document.getElementById('DaInserting').innerHTML += num;

        this.setState({xxxxx:ss})
    }

    deAnswer = () => {


        let SecondNum = this.state.xxxxx;

        this.setState({v2:SecondNum})



        console.log("deSecond :" + this.state.v2);
        console.log("this.setState.isCal :" + this.state.isCal);


        let answer = 0;
        this.setState.v2 = this.state.xxxxx;
        if(this.state.isCal==="add") {
            answer = this.state.v1 + this.state.v2;
            console.log("and the answer is :" + answer);
        }

        document.getElementById('DaInserting').innerHTML = answer;

        //this.thenReset();
    }

    thenReset = () => {
        if (this.setState.isCal !== null) {
            this.setState({xxxxx:0})
            this.setState({v1:0})
            this.setState({v2:0})
            this.setState({isCal:null})
        }
    }



    render() {
        //console.log("\nALL states\nthis.state.xxxxx :" + this.state.xxxxx + "\nthis.state.v1 :"+ this.state.v1);

        return (
            <div className="container">
            <div className="row">
                <div style={{height:64}} className="col-12 card-body m-2 shadow-sm rounded bg-light" id="DaInserting"> </div>
            </div>    
            <div className="row">
                <div className="col-3"><div onClick={() => this.okInsert(7)} className="btn btn-secondary btn-lg m-2 btn-block ">7</div></div>
                <div className="col-3"><div onClick={() => this.okInsert(8)} className="btn btn-secondary btn-lg m-2 btn-block ">8</div></div>
                <div className="col-3"><div onClick={() => this.okInsert(9)} className="btn btn-secondary btn-lg m-2 btn-block ">9</div></div>
                <div className="col-3"><div onClick={this.makeDivide} className="btn btn-primary btn-lg m-2 btn-block ">/</div></div>
            </div>

            <div className="row">
                <div className="col-3"><div onClick={() => this.okInsert(4)} className="btn btn-secondary btn-lg m-2 btn-block ">4</div></div>
                <div className="col-3"><div onClick={() => this.okInsert(5)} className="btn btn-secondary btn-lg m-2 btn-block ">5</div></div>
                <div className="col-3"><div onClick={() => this.okInsert(6)} className="btn btn-secondary btn-lg m-2 btn-block ">6</div></div>
                <div className="col-3"><div onClick={this.makeMultiply} className="btn btn-primary btn-lg m-2 btn-block ">x</div></div>
            </div>

            <div className="row">
                <div className="col-3"><div onClick={() => this.okInsert(1)} className="btn btn-secondary btn-lg m-2 btn-block ">1</div></div>
                <div className="col-3"><div onClick={() => this.okInsert(2)} className="btn btn-secondary btn-lg m-2 btn-block ">2</div></div>
                <div className="col-3"><div onClick={() => this.okInsert(3)} className="btn btn-secondary btn-lg m-2 btn-block ">3</div></div>
                <div className="col-3"><div onClick={this.makeSubtract} className="btn btn-primary btn-lg m-2 btn-block ">-</div></div>
            </div>

            <div className="row">
                <div className="col-6"><div onClick={this.deAnswer} className="btn btn-warning btn-lg m-2 btn-block ">=</div></div>
                {/* <div className="col-3"><div onClick={() => this.okInsert(".")} className="btn btn-secondary btn-lg m-2 btn-block ">.</div></div> */}
                <div className="col-3"><div onClick={() => this.okInsert(0)} className="btn btn-secondary btn-lg m-2 btn-block ">0</div></div>
                <div className="col-3"><div onClick={this.makeAdd} className="btn btn-primary btn-lg m-2 btn-block ">+</div></div>
            </div>

            </div>

        );
    }
}

export default calculator;

I have a div called "DaInserting" while gets updated as the user clicks on any number keys, then when the user will click on addition/subtraction/etc, v1 should get updated, but it doesn't. It gets updated on the second click:- check console.log.

After the user has clicked additions/subtraction, "DaInserting" gets a reset, user clicks some numbers again, and then clicks the result button which should then check what the user choose (addition/subtraction)

Henry Woody
  • 14,024
  • 7
  • 39
  • 56
adnan tariq
  • 197
  • 2
  • 13
  • 2
    `setState` is async, so any console.log immediately after `setState` is not guaranteed to display the new value. To check it, either use the callback or try logging it inside the `render` method. – Panther Jun 23 '19 at 02:29

2 Answers2

2

In React, setState is asynchronous (see these docs for more on this), so you cannot rely on state updating right after calling setState. Instead, you generally need to wait until the next render for state values to update.

To get around this, you can pass a callback function (as a second argument) to setState, or use await, or use the new values that you're updating state with directly. Here I'd recommend using that last option. Also, it's usually a good idea to only call setState right before exiting a function, to avoid async confusion.

Here is an example how you might update the deAnswer method:

deAnswer = () => {
    let answer = 0;
    if (this.state.isCal === "add") {
        answer = this.state.v1 + this.state.xxxxx;
        console.log("and the answer is :" + answer);
    }

    this.setState({
        v2: this.state.v1,
        answer: answer,
    })
}


render() {
    return (
        <div className="row">
            <div id="DaInserting">{ this.state.answer }</div>
        </div>
    )
}

Note also I've removed the setting of innerHTML, since you can and should change the content of the "DaInserting" div through rendering with state variables, not direct DOM manipulation.

Also consider checking out the Intro to React Tutorial, it's a nice read and will help you in learning and building with React.

Henry Woody
  • 14,024
  • 7
  • 39
  • 56
  • thank you sir. confused about one thing, what's the harm in using innerHTML? – adnan tariq Jun 23 '19 at 02:50
  • @adnantariq there is the possibility of vulnerability to XSS attacks when using `innerHTML`, though that's not really a concern here. Mostly the problem is that it's not best practice, whereas using `state` and `render` to modify displayed content is. In more complex examples, using `innerHTML` can become cumbersome and make it harder to read the code. – Henry Woody Jun 23 '19 at 02:52
2

It tested your code and it is updating the state correctly, remember that setState() is an asynchronous function, so if you wanna check correctly add the function as a callback to be executed after the state is updated, like this:

this.setState({ v1:firstNum, xxxxx:0, isCal:"add"}, () => {
      console.log("Add, firstNum is " + firstNum );
      console.log("and v1 is: " + this.state.v1);
      console.log("and isCal is: " + this.state.isCal);
      console.log(news)
});