Search code examples
javascriptreactjs

React Input component for currency number field to display from 0.01, 0.12, 1.23, 12.34 and so on


I want to create an input React component for a monetary amount and I would like the placeholder to be 0.00 but upon typing, the value should be updated digit by digit without the need to type a .. So, for example, an amount of 1,234.56 should show as:

0.01
0.12
1.23
12.34
123.45
1,234.56

And when erasing a number then the revert order:

1,234.56
123.45
12.34
1.23
0.12
0.01

But, unfortunately, I can't get any of it to work. I tried an onChange event and onKeyDown event too with no success. I am using a ShadCN component if that matters


Solution

  • You could format the currency with a number formatter, and parse it with parseInt.

    You will need to format on input, and validate on keydown.

    const currencyFormatter = new Intl.NumberFormat('en-US', {
      style: 'currency',
      currency: 'USD',
      minimumFractionDigits: 2
    });
    
    initCurrencyFields();
    document.forms['receipt'].addEventListener('submit', onSubmit);
    
    function onSubmit(e) {
      e.preventDefault();
      const values = [...e.target.elements]
        .filter(el => el.classList.contains('currency-input'))
        .map(el => el.value || '?');
      console.log(...values);
    }
    
    function initCurrencyFields(selector = '.currency-input') {
      document.querySelectorAll(selector)
        .forEach(el => {
          el.placeholder = '$0.00';
          el.addEventListener('input', formatCurrency);
          el.addEventListener('keydown', validateInput);
        });
    }
    
    function formatCurrency(e) {
      const inputField = e.target;
      const number = parseInt(inputField.value.replace(/[^0-9]/g, ''), 10);
    
      inputField.value = !isNaN(number)
        ? currencyFormatter.format(number / 100)
        : ''; // Clear field if input is not a valid number
    }
    
    function validateInput(e) {
      // Allow only number keys, control keys, and decimal points
      if (!/[0-9]|Backspace|ArrowLeft|ArrowRight|Delete|Tab|Enter/.test(e.key)) {
        e.preventDefault(); // Prevent non-numeric and non-control keys
      }
    }
    .as-console-wrapper { max-height: 4.33rem !important; }
    
    *, *::before, *::after {
      box-sizing: border-box;
    }
    
    html, body {
      width: 100%;
      height: 100%;
      padding: 0;
      margin: 0;
    }
    
    body {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: flex-start;
      padding: 1rem;
    }
    
    form[name="receipt"] {
      display: grid;
      grid-template-columns: auto 1fr;
      gap: 0.5rem;
    }
    
    form[name="receipt"] > button[type="submit"] {
      grid-column: 1 / 3;
    }
    <form name="receipt">
      <label for="item-a">Item A:</label>
      <input class="currency-input" id="item-a" name="a" type="text">
      <label for="item-b">Item B:</label>
      <input class="currency-input" id="item-b" name="b" type="text">
      <label for="item-c">Item C:</label>
      <input class="currency-input" id="item-c" name="c" type="text">
      <button type="submit">Submit</button>
    </form>