Reputation: 13
Sorry for the simplicity of this question, I am pretty new to React. I built a calculator with two input fields and then buttons to handle the different calculations. Now, I am transforming it into a more fully functional calculator with a whole keypad. In my code below, I am pretty sure that the way I have the "onClick" function written is the problem, but I am not sure how it is supposed to be written. (most of the code in the sample has to do with the initial project, and is not relevant to this question, I just wanted to include the whole component for good measure.)
When I click on the "1" button, it console logs 1 the first time, but the 1 at the top of the page doesn't show up until the second click. What do I have going on here that isn't allowing the one to display at the top until I click the "1" button the second time?
First Click: App after first click on 1
Second Click: App after second click on 1
class Calculator extends React.Component {
constructor(props) {
super(props);
this.setNum1 = this.setNum1.bind(this);
this.setNum2 = this.setNum2.bind(this);
this.add = this.add.bind(this);
this.subtract = this.subtract.bind(this);
this.multiply = this.multiply.bind(this);
this.divide = this.divide.bind(this);
this.clear = this.clear.bind(this);
this.buttonPressed = this.buttonPressed.bind(this);
this.state = {
pressed: "",
result: "",
num1: "",
num2: "",
};
}
buttonPressed(e) {
e.preventDefault();
const pressed = e.target.name;
const result = this.state.pressed;
this.setState({ pressed });
this.setState({ result });
console.log(e.target.name);
}
setNum1(e) {
const num1 = e.target.value ? parseInt(e.target.value) : "";
this.setState({ num1 });
}
setNum2(e) {
const num2 = e.target.value ? parseInt(e.target.value) : "";
this.setState({ num2 });
}
add(e) {
e.preventDefault();
const result = this.state.num1 + this.state.num2;
this.setState({ result });
}
subtract(e) {
e.preventDefault();
const result = this.state.num1 - this.state.num2;
this.setState({ result });
}
multiply(e) {
e.preventDefault();
const result = this.state.num1 * this.state.num2;
this.setState({ result });
}
divide(e) {
e.preventDefault();
const result = this.state.num1 / this.state.num2;
this.setState({ result });
}
clear(e) {
e.preventDefault();
this.setState({ num1: "", num2: "", result: 0 });
}
render() {
const { num1, num2, result } = this.state;
return (
<div>
<h1>{this.state.result}</h1>
<input onChange={this.setNum1} value={num1}></input>
<input onChange={this.setNum2} value={num2}></input>
<br />
<button name="1" onClick={this.buttonPressed}>
1
</button>
<button onClick={this.add}>+</button>
<button onClick={this.subtract}>-</button>
<button onClick={this.multiply}>x</button>
<button onClick={this.divide}>/</button>
<button onClick={this.clear}>clear</button>
</div>
);
}
}
export default Calculator;
If anyone could help me learn what I need to do to fix this type of situation I would be so grateful!
Upvotes: 1
Views: 10324
Reputation: 878
Reasoning:
Block that's causing issue
buttonPressed(e) {
e.preventDefault();
const pressed = e.target.name;
const result = this.state.pressed;
this.setState({ pressed });
this.setState({ result });
console.log(e.target.name);
}
Reason: setState is going to take a while to update the state object values (i.e.) setState does not update state object immediately as its async in nature.
reference: Why React setState/useState does not update immediately - blog
Possible solution cases for this issue are listed below
Case A: setting 'result' state object based on 'pressed' state object
Note: setting state of other object once target object state change was set using call back function
Objective: set 'result' state object only after 'pressed' state object was set
Implementation: Once 'pressed' state was set, we execute a call back function to set 'result' state object
Syntax:
this.setState(setTargetStateObject, function() { // execute actions that has to be done after setTargetStateObject becomes completed });
Solution:
buttonPressed(e) {
e.preventDefault();
const pressed = e.target.name;
this.setState({ pressed }, function() {
this.setState({ result: this.state.pressed });
});
}
Case B: setting result state object based on passed 'e' arg value
Solution:
buttonPressed(e) {
e.preventDefault();
const pressed = e.target.name;
// const result = this.state.pressed;
// directly assign the 'e' arg value to the result const variable
const result = pressed;
// 'result' state object is not dependent on 'pressed' state object
this.setState({ pressed });
this.setState({ result });
console.log(e.target.name);
}
Note: I would suggest you to use Functional component as you can have a clean code with less number of lines, use newly introduced hooks by React team, easier to write tests and most importantly for the performance too.
Created this sandbox for you calculator pgm - class vs functional react component - you can view how the same program of yours be implemented in class vs functional components.
References:
Upvotes: 6
Reputation: 684
The problem here is in
buttonPressed(e) {
e.preventDefault();
// here e.target.name is 1 so value in const pressed becomes 1
const pressed = e.target.name;
// currently this.state.pressed is "" so result becomes "" if you want to show
// first number as result use const result = e.target.name
const result = this.state.pressed;
this.setState({ pressed });
this.setState({ result });
console.log(e.target.name);
}
You are setting your result to this.state.pressed which is empty during the first time so it sets result as empty string but during second time as this.state.pressed is 1 so it sets result as 1 hence its shown on second click
Upvotes: 2