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

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.
Inspecting the html, I see the value is correct:
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.

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);
};
```

Related

How do I format an account name and/or sort code using Javascript?

I am posting this question with my answer so far but would like to invite other solutions as I am not 100% sure about mine.
It will:
Automatically place the dashes in the right place so the user only has to enter the digits.
Can be any size. You can set a maxlength attribute on your input and it will continue to apply dashes intil it runs out of space. It will default to 8 characters max
Allowsuser to delete digits without the need to delete the dashes too.
Why am I posting a this?
I could not find the answer myself on StackOverflow and when you search this question on Google, it keeps returning a PHP answer for StackOverflow instead! There are even answers in there for Javascript.
Hopefully this question can produce other solutions too!
How does it work?
This is designed to work with a real-time input.
It works out the maximum length
It captures the event and works out if the delete key was pressed
The 'regex' part is saying to replace ever 2nd character with itself
plus a dash.
The next line first replaces anything that's not a number, then uses the regex to inject dashes and finally the string is sliced to remove any trailing slash
You would apply this function to your onkeyup or onpaste events, passing 'this' in.
function checkSortCode(el,ev){
var len = el.maxLength || 8;
ev = ev || window.event;
if(ev.keyCode == 8 && el.value.slice(-1) == "-"){
el.value = el.value.slice(0,-1);
} else {
var regex = new RegExp("(\\S{" + (2 - 1) + "}\\S)", "g");
el.value = el.value.replace(/[^0-9]/g,"").replace(regex,("$1"+"-")).slice(0,len);
}
}
.sortcode::placeholder{color:#eeeeee;}
body{font-family:arial,sans-serif;font-size:1.4em;}
input{font-size:1.4em;}
<label>Sort Code</label><br>
<input type="text" name="sortcode" onkeyup="checkSortCode(this,event)" onpaste="checkSortCode(this,event)" class="sortcode" size="8" maxlength="8" placeholder="00-00-00" />
Ideally, I wanted it to show the 00-00-00 format all the time and then the user would fill it in but have padded zeros where they hadn't. That's not easy as the cursor wants to go to the end of the input.
What you're looking for is called Input Masking. You can implement it yourself but I would recommend using a library to separate the actual input value and the mask.
Here an implementation using native js, you'll notice it's a bit janky.
<html>
<body>
<input id="input">
<script>
const pattern = '00-00-00-00'
const patternRegex = /^[0-9]{2}\-[0-9]{2}\-[0-9]{2}\-[0-9]{2}$/
const separator = '-'
/* returns current value completed by the pattern (filled with 0) */
const fill = value => {
return `${value}${pattern.substring(value.length)}`
}
/* format the input on keyup */
const format = event => {
/* only format the input at cursor position (to ignore filled pattern) */
const position = event.target.selectionStart
const value = event.target.value.substring(0, position)
/* rollback invalid inputs */
if (!patternRegex.test(fill(value))) {
event.target.value = event.target.value.substring(0, position - 1)
return
}
/* change target valuer to include pattern and restore carret position */
event.target.value = fill(value)
const newPosition = event.target.value[position] === separator ? position + 1 : position
event.target.setSelectionRange(newPosition, newPosition)
}
const input = document.getElementById('input')
input.addEventListener('keyup', format)
</script>
</body>
</html>
You can check some other implementation here : https://css-tricks.com/input-masking/
The reason why it's janky is because we format the input after a change occured. When using a library (or React), you can control the input value before it's rendered.

How can I mask any element, not only inputs, in React

I'm making an admin dashboard with NextJS and MaterialUI (mui), and I need to mask some values (eg.: phone numbers) that comes from back-end without mask.
I'm using react-input-mask for input elements, but I don't know how to mask any element (for example, a span inside a table cell).
For example, my back-end gives me a phone number like 5511912345678 and a document number like 12345678900, and I need to display it in a table this way:
First name
Last name
Phone number
Document
John
Doe
+55 (11) 9.1234-5678
123.456.789-00
Lorem
Ipsum
+55 (11) 1234-5678
234.567.891-00
How can I mask that values on a table-cell without an <input> element? I assume that will not be possible using react-input-mask, so there's no problem adding another package. I just don't know what package to install to achieve this result.
Thanks!
You can either use a library from NPM or build your own masking function. Here is a naïve implementation of the masking functions. Here is a working example and following are the important snippets from it.
digitsExtractor will return a wrapped object which will help in pulling out digits from the number.
function digitsExtractor(numberString) {
const digitsInReverse = numberString.split('').reverse();
const extractDigits = (count = 0) => {
const digits = [];
while (count-- > 0) digits.push(digitsInReverse.pop());
return digits.join('');
};
return {
extractDigits,
getRemainingDigitsCount: () => digitsInReverse.length,
};
}
maskPhoneNumber will mask the phone number
function maskPhoneNumber(phoneNumber) {
const { extractDigits, getRemainingDigitsCount } =
digitsExtractor(phoneNumber);
return `+${extractDigits(2)} (${extractDigits(2)}) ${
getRemainingDigitsCount() === 8
? [extractDigits(4), extractDigits(4)].join('-')
: getRemainingDigitsCount() === 9
? `${extractDigits(1)}.${extractDigits(4)}-${extractDigits(4)}`
: ''
}`;
}
maskDocumentNumber will mask the document number
function maskDocumentNumber(documentNumber) {
const { extractDigits } = digitsExtractor(documentNumber);
return [
[extractDigits(3), extractDigits(3), extractDigits(3)].join('.'),
extractDigits(2),
].join('-');
}
You can make it more generic based on your requirements.

How to format the TextInput component value in react native?

I use this function to format every number input I have :
export const parseToNumber = (value: string) => {
const numberRegex = /^[0-9\b]+$/;
if (numberRegex.test(value)) {
return value;
} else {
return value.replace(/\D/g, "");
}
};
And I use it with another function to have a an empty space between every 4 characters like this :
onChangeText={(e) => {
setFieldValue(
"phone",
parseToNumber(e).replace(/(.{4})/g, "$1 ")
);
}}
The problem is the Backspace won't work because of the parseToNumber . How can I exclude the functions when the user presses the Backspace and use the simple way like this setFieldValue("phone", (e)); so the backspace works ?
Or is there another way to fix the Backspace disabled problem with these functions in place possibly modify the parseToNumber function ?
If Problem is parseToNumber, you can make sure only take numbers by opening numbers only keyboard by passing prop
keyboardType = 'numeric'
or to simplify the use and support multiple spacing for input you can use this library - react-native-mask-input, its js only library so you can also just copy the specific implementation on need to use basis.

How to format number correctly in a calculator app (React)?

This is my first time posting in SO, and I need help formatting numbers correctly in a calculator app that I've made using ReactJS.
Here is the link on StackBlitz.
Now, I want to achieve the formatting effect after numbers are pressed and shown in the display and arithmetic signs are added, especially when multiple arithmetics are used.
To illustrate my point, below is a sample of the current display:
123456 + 7890123 * 11111
And what I want to achieve is this:
123,456 + 7,890,123 * 11,111
I could only do this when displaying the result using the toLocaleString() function. Even then, if I pressed number/numbers and then clicking the result button twice, it will be crashed (as the display contains a comma, and the evaluation function will not process it properly).
Hopefully, someone can point me out in the right direction. Thanks.
Quick Fix
You can remove ',' before evaluating the result.
Change line 65 of Input.js to
setDisplay(evaluate(display.replace(/,/g, '')).toLocaleString());
Better Solution
Keep separate variables for Internal logical state and External Display state, where the former is valid for code and the latter is its visual representation.
You can achieve this by useEffect like this
/* --- Display.js --- */
const Display = ({ display }) => {
const [printValue, setPrintValue] = useState('')
useEffect(() => {
setPrintValue(`${display}`.replace(/[0-9]+/g, num => (+num).toLocaleString()))
}, [display])
return (
<StyledDisplay>
{' '}
<span>{printValue}</span>{' '}
</StyledDisplay>
);
};
Also, in Input.js, update line 65 in handleResult to
setDisplay(evaluate(display));
For this kind of situations, I like to use regex. Here what you can do is to use a regex that matches 3 digits and add the comma as wanted. To simplify the regex I usually reverse the string:
const original = "123456 / 98765 * 22222"
function format(str) {
const reversed = str.split('').reverse().join('')
const formatted = reversed.replace(/(\d{3})(?=\d)/gm, `$1,`)
return formatted.split('').reverse().join('')
}
console.log('original string : ', original)
console.log('result string : ',format(original))
You can use this function in your Display component, just before injecting the display prop like this
function format(str){
const reversed = str.split('').reverse().join('')
const formatted = reversed.replace(/(\d{3})(?=\d)/gm, `$1,`)
return formatted.split('').reverse().join('')
}
const Display = ({ display }) => {
return (
<StyledDisplay>
{' '}
<span>{format(display)}</span>{' '}
</StyledDisplay>
);
};

Force TextField with type number to display dot as decimal separator

I am using material-ui to render a TextField component in my react app. It's somehow forcing all the <TextField type="number /> to have decimal separator as comma (,) instead of dot (.) which is confusing for the targeted users.
Is there any way to force it to always show dot as the decimal separator, regardless of locale?
I have created a small example here. just try to enter a number with decimals and click outside and it'll convert it into comma. This is probably because of my current device locale, but still I want to force a dot for everyone.
For me, setting the language attribute to "en-US" in the inputProps worked:
<TextField
type="number"
inputProps={{step: "0.1", lang:"en-US"}}
/>
As per my suggestion of using a third-party library to be integrated in the Textfield component; You could use the react-number-format library as an inputProp for the Textfield. It works because the number formatter offered by the aforementioned library has a decimalSeparator prop which defaults to the one character string "."
The custom number formatter would go like this:
import NumberFormat from 'react-number-format';
interface NumberFormatCustomProps {
inputRef: (instance: NumberFormat | null) => void;
onChange: (event: { target: { value: string } }) => void;
}
function NumberFormatCustom(props: NumberFormatCustomProps) {
const { inputRef, onChange, ...other } = props;
return (
<NumberFormat
{...other}
getInputRef={inputRef}
onValueChange={values => {
onChange({
target: {
value: values.value
}
});
}}
isNumericString
/>
);
}
And it could be used in a number Textfield like this:
<TextField
label="react-number-format"
value={values.numberformat}
onChange={handleChange("numberformat")}
id="formatted-numberformat-input"
InputProps={{
inputComponent: NumberFormatCustom as any
}}
type="number"
/>
For a fully functioning example, check out this sandbox.
I came out with solution that accepts both , and . separators using regex validator.
In addition of controlling input label, I control external numeric state <number | null>.
In my solution user can only input decimals separated . or , The input label and numeric state only changes if user input is convertable to float, In case of empty "" input, numeric state is set to null.
const [inputValue, setInputValue] = useState<string>("")
const [numericValue, setNumericValue] = useState<number | null>(null)
return (<TextField
inputMode={"numeric"}
onChange={(e) => {
e.preventDefault();
// if new value is valid float
if (/^([\d]*[,.]?[\d]{0,2})$/.test(e.target.value)) {
setInputValue(e.target.value);
const parsed = parseFloat(
e.target.value.replaceAll(",", ".")
);
setNumericValue(isNaN(parsed) ? null : parsed)
}
}}
value={inputValue}
/>);

Categories