Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"Intl.NumberFormat based formatting" doesn't work #867

Open
6 tasks done
gabriel-faria-zipline opened this issue Nov 23, 2024 · 2 comments
Open
6 tasks done

"Intl.NumberFormat based formatting" doesn't work #867

gabriel-faria-zipline opened this issue Nov 23, 2024 · 2 comments

Comments

@gabriel-faria-zipline
Copy link

gabriel-faria-zipline commented Nov 23, 2024

Describe the issue and the actual behavior

When using Intl.NumberFormat based formatting just like it's demo'ed in your docs, using decimals break the form.

If you change that demo to accept 2 decimals, as currencies as used, every key stroke will multiply the value by 100.

Describe the expected behavior

It should handle cents as normal, without multiplying the value.

Provide a CodeSandbox link illustrating the issue

Open your own sandbox and just change maximumFractionDigits: 2 to accept cents

Provide steps to reproduce this issue

Type numbers with cents.

Please check the browsers where the issue is seen

  • Chrome
  • Chrome (Android)
  • Safari (OSX)
  • Safari (iOS)
  • Firefox
  • Firefox (Android)

I've spent some time troubleshooting, and the issue is with your defaultRemoveFormatting, which is called in this line.

What that is doing is:

  1. The user types 1.1.
  2. _numAsString is set to 1.1.
  3. const _formattedValue = _format(_numAsString); is called, so _formattedValue is set to $1.1
  4. _numAsString = removeFormatting(_formattedValue, undefined); is called with the default formatting function, which just grabs the digits from the string. So now _numAsString is set to 11.
  5. Now the input has value of $11 instead of $1.1.

The Intl library doesn't provide a unformatter function. But there's not even a real need, since _numAsString already had the correct value and it's erased. Creating a custom unformatter function would add complexity for no gain (especially because handling i18n strings is dangerous and bug-prone).

I propose a couple ways to fix this:

  • Offer a prop to turn off the removeFormatter function. Essentially turn of recalculating the value after the formatter runs. That feels like an edge case.
  • Alternatively, just allow for the formatter to return the new _numAsString. For example in the case above it could return:
return {
  formattedNum: "$1.1",
  numAsString: "1.1",
}

that would allow your use case where the formatter erases digits, without the need to call removeFormatting.

We're blocked on using the Intl library until this is fixed. So a quick response would be greatly appreciated! Or at least a work around.
Thanks!

@adamdharrington
Copy link

I'd like to see a proper solution in the library too but can proceed with this workaround in userland:

  // every time language changes (or currency or whatever) generate your format and removeFormatting functions
  const [format, removeFormatting] = useMemo(() => {
    const formatter = new Intl.NumberFormat(language, {
      style: 'currency',
      currency: currencyCode,
      maximumFractionDigits: 2,
      minimumFractionDigits: 2,
    });

    let decimalSeparator = '';
    formatter.formatToParts(1000000.00).forEach((part) => {
      if (part.type === 'decimal') {
        decimalSeparator = part.value;
      }
    });

    const format = (numStr: string): string => {
      if (numStr === '') return '';
      return formatter.format(Number(numStr));
    }

    // keep only unicode numeric characters and locale-specific decimals
    // then replace decimal with "." for conversion to JS float
    const unformatRegex = new RegExp(`[^\\p{N}^${decimalSeparator}]`, 'gu');
    const removeFormatting = (numStr: string): string => {
      return numStr.replace(unformatRegex, '').replace(decimalSeparator, '.');
    }

    return [format, removeFormatting];
    
  }, [language, currencyCode])

@s-yadav
Copy link
Owner

s-yadav commented Dec 25, 2024

Intl.NumberFormat is not natively supported on the library at the moment.
As mentioned in the doc this is just a basic implementation example.

To give an example a basic implementation of number formatting if we have simple use case to format number without decimals and negative number support.

On the long term though internally would refactor the lib to use Intl.NumberFormat so local handling is more standardised, but this is a long way.
Happy to collaborate if someone wants to pick this up.

Meanwhile, @adamdharrington would appreciate it if you could create and sandbox for the above shared example and link here.

Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants