I just had the same problem and discovered an alternative solution using the validation API - works without black magic in all major browsers (Chrome, Firefox, Safari) except IE. This solution simply prevents users from entering invalid values. I also included a fallback for IE, which is not nice but works at least.
Context: onInput function is called on input events, setInputValue is used to set the value of the input element, previousInputValue contains the last valid input value (updated in setInputValue calls).
function onInput (event) {
const inputValue = event.target.value;
// badInput supported on validation api (except IE)
// in IE it will be undefined, so we need strict value check
const badInput = event.target.validity.badInput;
// simply prevent modifying the value
if (badInput === true) {
// it's still possible to enter invalid values in an empty input, so we'll need this trick to prevent that
if (previousInputValue === '') {
setInputValue(' ');
setTimeout(() => {
setInputValue('');
}, 1);
}
return;
}
if (badInput === false) {
setInputValue(inputValue);
return;
}
// fallback case for IE and other abominations
// remove everything from the string expect numbers, point and comma
// replace comma with points (parseFloat works only with points)
let stringVal = String(inputValue)
.replace(/([^0-9.,])/g, '')
.replace(/,/g, '.');
// remove all but first point
const pointIndex = stringVal.indexOf('.');
if (pointIndex !== -1) {
const pointAndBefore = stringVal.substring(0, pointIndex + 1);
const afterPoint = stringVal.substring(pointIndex + 1);
// removing all points after the first
stringVal = `${pointAndBefore}${afterPoint.replace(/\./g, '')}`;
}
const float = parseFloat(stringVal);
if (isNaN(float)) {
// fallback to emptying the input if anything goes south
setInputValue('');
return;
}
setInputValue(stringVal);
}