kilinkis
kilinkis

Reputation: 602

`toLocaleString()` not rendering inside number input when value greater than 1 million

In this code, I have the value 1000000 (one million) which I format with toLocaleString('en-gb') to get 1,000,000.

Then I print that value as text and it works as expected, also when I use that const as the value of a text input. But when using the value in a numeric input, it just doesn't render. It works though when the value is < 1 million.

enter image description here

Inspecting the html, I see the value is correct: enter image description here

In addition, when trying to type the value in that numeric input, it doesn't register values after 4 digits.

Any ideas what's going on? I wonder if it could be that after 999.000 the number will have two thousand-separators

teh codez (also in this playground https://stackblitz.com/edit/react-ts-8ufbe1?file=App.tsx):

export default function App() {
  const value = (1000000).toLocaleString('en-gb');
  const [inputValue, setInputValue] = React.useState(value);

  return (
    <div>
      <h1>{value}</h1>
      <input
        type="number"
        value={inputValue}
        onChange={(e) => setInputValue(Number(e.target.value).toLocaleString('en-gb'))}
      />
      <input type="text" value={inputValue} />
    </div>
  );
}

I see there are libraries like react-number-format but seems like the native way should do what I need.

Thank you very much in advance.

Upvotes: 0

Views: 1708

Answers (1)

Zargold
Zargold

Reputation: 2092

It works before reaching 1,000 because 1,000 is where you have to add a comma. At 999 you still satisfy the requirements of an input with type number you're inserting only a number 999. Since HTML only really allows strings they let you pass with "999" vs 999 the pure numeric version.

In any case. I don't think you want to use the type='number' it works pretty poorly and shows the ticker up and down. If this is a mobile focused decision consider using type='tel'.

What you can also do is attempt to manually mask the behavior by putting 2 inputs on top of each other. Or you can use an input type='tel' onFocus={() => Number(inputValue)} and onBlur={() => inputValue.toLocaleString('en-gb')} and that will be the start to getting where you want to get.

This type of stuff usually is slightly messy and will take a bit to perfect. The UI can easily get wonky, and the problem is not so perfectly solved:

  • We want to enforce that users only type numbers and make it easy for them to do so (especially mobile)
  • We want the numbers to appear as strings. Those 2 priorities are at odds with each other.

If you don't want to directly import a library which contains this I'd strongly suggest you read their source code.

Some potential resources: https://cchanxzy.github.io/react-currency-input-field/ (Note that just the single component is 600 lines long)

including this excerpt:

const handleOnFocus = (event: React.FocusEvent<HTMLInputElement>): number => {
      onFocus && onFocus(event);
      return stateValue ? stateValue.length : 0;
    };

    /**
     * Handle blur event
     *
     * Format value by padding/trimming decimals if required by
     */
    const handleOnBlur = (event: React.FocusEvent<HTMLInputElement>): void => {
      const {
        target: { value },
      } = event;

      const valueOnly = cleanValue({ value, ...cleanValueOptions });

      if (valueOnly === '-' || !valueOnly) {
        setStateValue('');
        onBlur && onBlur(event);
        return;
      }

      const fixedDecimals = fixedDecimalValue(valueOnly, decimalSeparator, fixedDecimalLength);

      const newValue = padTrimValue(
        fixedDecimals,
        decimalSeparator,
        decimalScale !== undefined ? decimalScale : fixedDecimalLength
      );

      const numberValue = parseFloat(newValue.replace(decimalSeparator, '.'));

      const formattedValue = formatValue({
        ...formatValueOptions,
        value: newValue,
      });

      if (onValueChange) {
        onValueChange(newValue, name, {
          float: numberValue,
          formatted: formattedValue,
          value: newValue,
        });
      }

      setStateValue(formattedValue);

      onBlur && onBlur(event);
    };
    ```

Upvotes: 1

Related Questions