Julie
Julie

Reputation: 514

How do I stop parseFloat from stripping zeros from decimal

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

Answers (2)

Matt Carlotta
Matt Carlotta

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

machineghost
machineghost

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

Related Questions