0

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;
Julie
  • 484
  • 7
  • 22
  • Given that we can trivially confirm that both `(2.5).toFixed(2)` and `(2.505).toFixed(2)` definitely give you `2.50` as result, as they're supposed to, simply by using the dev tools console: are you _sure_? (Remember, numbers don't have "trailing zeroes", only strings do) – Mike 'Pomax' Kamermans Jul 15 '21 at 04:01
  • try converting the whole result : setTotal((parseFloat(balance).toFixed(2) + parseFloat(newBalance).toFixed(2)).toFixed(2)); – Tushar Shahi Jul 15 '21 at 04:03
  • 1
    You should convert after calculating, try `parseFloat(balance + newBalance).toFixed(2)` – Anh Nhat Tran Jul 15 '21 at 04:07
  • @AnhNhatTran I thought your solution worked, but when it comes to adding who numbers it just adds the number to the second decimal place, if I add 2 + 5.50 I get 5.52 – Julie Jul 15 '21 at 04:27

2 Answers2

1

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.

machineghost
  • 33,529
  • 30
  • 159
  • 234
  • can you give an example of how to display in JSX I tried `state.addToBalChecked && addBal().toFixed(2)` but got an error `cannot read prop...2Fixed of undefined` – Julie Jul 15 '21 at 04:47
  • It sounds like `addBal()` is returning `undefined`. – machineghost Jul 15 '21 at 05:00
  • no the error was `TypeError: Cannot read property 'toFixed' of undefined` nothing to do with `addBal()` – Julie Jul 15 '21 at 05:04
  • Oh, on reading that error more carefully I can see that the error says `2Fixed`, *not* `toFixed`. So evidently you have a prop called `2Fixed` (with a numeral) that is undefined. In either case, your original question has been answered, and if you have new questions you should really start a fresh SO question with that code. – machineghost Jul 15 '21 at 15:33
0

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.

Matt Carlotta
  • 18,972
  • 4
  • 39
  • 51