Reputation: 514
In my app, I want to let the user update an old amount. For example, if the amount was 2.00 and the user adds .50 cents it should read 2.50 when the user hits the total button, but I am getting 2.5
instead, where the zero is stripped off by the parsFloat
. I tried toFixed(2)
:
setTotal(parseFloat(balance).toFixed(2) +
parseFloat(newBalance).toFixed(2);
but it is not working, I get 2.505
for example, not 2.50
. I am trying to write a function that will add the zeros after checking if the total has a decimal in it. I have multiple functions to add or subtract, so I need the function to work for all, in the below example I have only one function. Any help is much appreciated.
Here is the simplifiled code:
const AddForm = () => {
const [newBalance, setNewBalance] = useState("");
const [total, setTotal] = useState("");
const { id } = useParams();
const history = useHistory();
const addBalHandler = (e) => {
e.preventDefault();
axios({
method: "PUT",
url: `http://localhost:5000/update-snapshot/${id}`,
data: {
date: startDate,
},
}).then((res) => {
history.push(`/success/` + id )
console.log(res.data);
});
};
const addBal = () => {
const hasDecimal = total.includes(".")
if ( hasDecimal ) {
// condition to add the zeros at right of decimal
}
setTotal(parseFloat(balance) +
parseFloat(newBalance));
};
return (
<form
action="/update-snapshot/:id"
method="post"
onSubmit={addBalHandler}
>
<Col>
<Form.Label>Balance: </Form.Label>
<Input
setInputValue={setNewBalance}
inputValue={newBalance}
inputName={"newBalance"}
inputType={"text"}
/>
</Col>
<Col>
<Input
setInputValue={setTotal}
inputValue={total}
inputName={"total"}
inputType={"text"}
/>
<Button
onClick={() => {
state.addToBalChecked && addBal();
}}
>
Calculate Total
</Button>
</Col>
<div>
<Button type="submit">
Save
</Button>
</div>
</form>
);
};
export default AddForm;
Upvotes: 0
Views: 781
Reputation: 19762
You can use Math.floor(100 * num)/100
† with toLocaleString.
// converts a string to a float rounded down to the nearest hundredth
const toFloatHundredth = str => Math.floor(100 * parseFloat(str)) / 100;
const oldValue = toFloatHundredth("2.009876");
const addValue = toFloatHundredth("0.506789");
const resultNum = Math.floor(100 * (oldValue+addValue)) / 100;
const resultStr = resultNum.toLocaleString('en-US',
{
style: 'decimal',
minimumFractionDigits: 2,
});
console.log(typeof resultNum + ": " + resultNum); // 2.5 number
console.log(typeof resultStr + ": " + resultStr); // 2.50 string
However, one thing to keep in mind is that you're using text
instead of a number
, so a number like 999.999,99
is a valid international number, but an invalid float (parsed result is 999.999
). Therefore you could avoid handling edge cases by using an input
of type number
with a step. By using type="number"
for your inputs it sanitizes and normalizes the input:
const AddForm = () => {
const [newBalance, setNewBalance] = React.useState("2");
const [addBalance, setAddBalance] = React.useState("0");
const [totalBalance, setTotalBalance] = React.useState("");
const toFloatHundredth = str => (100 * (Math.floor(parseFloat(str)+'e2')+'e-2'))/100
const toEnUSLocale = num => num.toLocaleString('en-US', {
style: 'decimal',
minimumFractionDigits: 2,
});
const getBalance = () => toEnUSLocale(toFloatHundredth(newBalance) + toFloatHundredth(addBalance))
const calculateBalance = () => {
setTotalBalance(getBalance());
};
const handleReset = () => {
setNewBalance("2")
setAddBalance("0")
setTotalBalance("");
}
const handleSubmit = event => {
event.preventDefault();
alert(`Saved: ${totalBalance || getBalance()}`);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="balance">Balance: </label>
<input
id="balance"
onChange={e => setNewBalance(e.target.value)}
value={newBalance}
type="number"
step="0.01"
/>
</div>
<div>
<label htmlFor="total">Add to Balance: </label>
<input
id="total"
onChange={e => setAddBalance(e.target.value)}
value={addBalance}
type="number"
step="0.01"
/>
</div>
{totalBalance && <div>Total Balance: {totalBalance}</div>}
<div>
<button
type="button"
onClick={calculateBalance}
>
Calculate Total
</button>
</div>
<div>
<button type="reset" onClick={handleReset}>
Reset
</button>
</div>
<div>
<button type="submit">
Save
</button>
</div>
</form>
);
};
ReactDOM.render(<AddForm />, document.body)
<script crossorigin src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
† Please note that floating point precision in Javascript is a bit quirky due to how its stored in memory.
Upvotes: 0
Reputation: 35740
Your problem is in toFixed
in this code:
setTotal(parseFloat(balance).toFixed(2) +
parseFloat(newBalance).toFixed(2);
From the MDN (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed):
Return value
A string representing the given number using fixed-point notation.
In other words, parseFloat
gives you a number, which you then call toFixed
on to create a string. When you add two strings together, they don't add the same way as two numbers.
If you add: "2.50" + "2.50"
, you get "2.502.50"
... not 5
.
You could of course use parseFloat
once again to convert those strings back into numbers, if that's what you're trying to do. However, I think a better solution would be to just not use toFixed
until the very end, at the point when you're ready to display the value to the user. In other words, don't use it on what you keep in your state, but instead use it to display the value in your JSX.
Upvotes: 1