Reputation: 153
I'm making a simple interest calculator, and I want it to calculate the interest immediately after the user fills in all the required fields.
The problem is that the when I calculate the interest and call setResult(theInterest) it doesn't show the result immediately, I have to type another number in one of the fields so the result updates, but then it will show the result based on the previous numbers (the previous result) not the new one.
Here is a link to my code: https://github.com/abdulaziz-sama/Interest-Calculator/blob/master/src/App.jsx
Upvotes: 0
Views: 182
Reputation: 214
The problem is the set method of the useState is running asynchronously.
Here is the working code:
function Calculator() {
const [result, setResult] = useState(0);
const [principal, setPrincipal] = useState(0);
const [rate, setRate] = useState(0);
const [time, setTime] = useState(0);
// This is just to show the async behaviour.
const [test, setTest] = useState(0);
function handleChange(e) {
const { name, value } = e.target;
// Putting "+" before a string converts it to a number ✨JS magic.
// But here we're testing if it's a valid number. If not we're not going to set anything.
if (Number.isNaN(+value)) {
return;
}
// We are sure that the +value is a valid number so we're setting that to the state.
if (name === "principal") {
setPrincipal(+value);
} else if (name === "rate") {
setRate(+value);
} else if (name === "time") {
setTime(+value);
}
}
// This effect will run everytime principal, rate or time changes.
// We dont need to convert values since we're always storing numbers in the states.
useEffect(() => {
var theInterest = principal * (rate / 100) * time;
setResult(theInterest);
}, [principal, rate, time]);
return (
<div className="container">
<h1>Calculate Simple Interest</h1>
<button
onClick={() => {
setTest(test + 1);
setTest(test + 1);
setTest(test + 1);
}}
>
test {test}
</button>
<label htmlFor="principal"> Initial Amount of Money: </label>
<input
onChange={handleChange}
type="number"
name="principal"
step="any"
placeholder="Enter Initial Amount"
value={principal}
/>
<label htmlFor="rate">Annual Interest Rate: </label>
<input
onChange={handleChange}
type="number"
name="rate"
step="any"
placeholder="Enter the rate"
value={rate}
/>
<label htmlFor="time">Time (in years):</label>
<input
onChange={handleChange}
type="number"
name="time"
placeholder="Enter the period"
value={time}
/>
<h2>The interest is: {result}</h2>
</div>
);
}
I added a test button in here, so you can see the behaviour of the setTest. I try to increment it 3 times but it will only do it once. That is because the 3 calls run at the same time and they do not see the other ones modifying it too.
Also change the "for" attributes on labels to be "htmlFor".
Upvotes: 1
Reputation: 1301
Use useEffect
to trigger the calculteInterest
function as you have state changes that rerender your component within your handleChange function.
it would look something like this:
useEffect(calculteInterest, [principal, rate, time])
And remove the function call from your handleChange function.
codesandbox example: https://codesandbox.io/s/beautiful-dust-zgkkz?file=/src/App.js
Upvotes: 1