I am using react 16.8.2 new hooks API. -Just for info-
My problem only involves JS.
I have two input fields. They take only numbers as inputs. If the user enters /\D+/ (non-digits), the field is set to ''(empty). If he enters 2.3393, the number should always be rounded to two decimal places 2.34
Field1: onChange formats the number to $ 32,233,233,322.24
Field2: onChange formats the number to 99%. Decimals places are simply truncated.
The Input field should be able to handle e.nativeEvent.inputType deleteContentBackward as well. Such that if the user is at $ 2 and deletes 2, Field1 becomes empty. Similarly for Field2. 1% on deleting % becomes empty.
So far I have this:
const handleInputChange = function (e) {
const val = e.target.value;
const formatValue = function () {
if (/.*\d?\.\d*/.test(val)) return val.replace(/(?<=\d?\.\d*)\..*/g, '');
return +val.replace(/\$\s?|(,+)|%/g, '');
};
if (formatValue()) {
if (fieldSuffix === 'Percentage') {
if (e.nativeEvent.inputType === 'deleteContentBackward') return setVal(`${formatValue()}%`.replace(/^\d%$|\d(?=%)/, ''));
return setVal(`${formatValue()}%`);
}
if (fieldSuffix === 'Dollars') return setVal(`$ ${formatValue()}`.replace(/\B(?=(\d{3})+(?!\d))/g, ','));
return setVal(formatValue());
}
return setVal('');
};
return (
<input
value={val}
onChange={handleInputChange}
/>
)
It does not work well for when user enters single .. $ are prepend for every . keystroke. The case that when user enters /\D+/ is not handled. % Field2 decimal place truncation case is also not handled. I can think of other cases also that are not handled.
My code is getting complicated. This approach is not elegant. Please Help.
The following code works well.
const handleInputChange = function (e) {
const formatValue = function () {
// Remove non-digit, except '.' and remove everything beginning from second decimal '.'
return e.target.value.replace(/[^0-9.]|(?<=^[^.]*\.[^.]*)\..*/g, '');
};
if (formatValue()) {
if (fieldSuffix === 'Percentage') {
// Percentage decimal truncated
const truncatedPercentage = formatValue().replace(/\..*/, '');
if (e.nativeEvent.inputType === 'deleteContentBackward') return setVal(`${formatValue()}%`.replace(/^\d%$|(\d|\.)(?=%)/, ''));
return setVal(`${truncatedPercentage}%`);
}
if (fieldSuffix === 'Dollars') {
// Truncated to two decimal places, not rounded
const truncatedDollar = formatValue().replace(/(?<=\.\d{2}).*/, '');
// Format and insert ','
return setVal(`$ ${truncatedDollar}`.replace(/\B(?=(\d{3})+(?!\d))/g, ','));
}
}
return setVal('');
};
I still feel that there is a lot of redundancy and unhandled cases in this code
Related
This question already has answers here:
Remove/ truncate leading zeros by javascript/jquery
(17 answers)
Closed 19 days ago.
I am a beginner in React JS. I have a use case in that I want to correct the number that a user enters in <input type='number> field.
By default, a user can enter numbers with leading zeros like 0002 or -0042, etc.
I want to make it such that the leading zeros are removed when the user enters the number. Also, the user should be able to enter decimal as well as negative numbers. I have done it using onBlur but I want to somehow do it onChange method itself.
onChange=()=>{ ... }
<input type = 'number' onChange={onChange}>
I want to make it such that the leading zeros are removed when the user enters the number.
You can remove the leading zeros with String.replace:
// ... code that obtains the user input in `inputText` ...
inputSanitisedText = inputText.replace(/^0+/, '')
(I am assuming you don't want to change the user's input while they're entering it. That would be very bad UI design.)
You can use regex to remove zeros from beginning: /^0+/
In your case:
onChange = (e) => {
const _removedZeros = e.target.value.replace(/^0+/, '')
///...
}
you can simply multiplied value to 1, like this :
const [value, setValue] = useState("");
<input
value={Boolean(value) ? value : ''}
type="number"
onChange={(e) => setValue(e.target.value * 1)}
/>
in this way user cannot type leading zeros
As per your description, you can solve this by using the parseFloat() function. This function will remove the leading zeros and will convert the input value to a decimal/fractional number.
The code should be like this:
const onChange = (event) => {
const value = parseFloat(event.target.value);
event.target.value = isNaN(value) ? '' : value;
};
something like this?
foo.oninput = (e) => {
const value = foo.value;
let [_, sign, integer, decimals] = value.replace(/[^\d\.\-]/g, "") // invalid characters
.replace(/(\..*?)\./g, "$1") // multiple dots
.replace(/(.+)-/g, "$1") // invalid signs
.match(/^(-?)(.*?)((?:\.\d*)?)$/);
let pos = foo.selectionStart - 1;
if(!integer && decimals) pos += 2;
// don't convert an empty string into a 0,
// unless there are decimal places following
if(integer || decimals) {
integer = +integer;
}
const formatted = sign + integer + decimals;
if(formatted !== value) {
foo.value = formatted;
foo.setSelectionRange(pos, pos);
}
}
<input type="text" id="foo" />
I'm make a react application that takes an input that should be shown to two decimal places. When I have a number that has a 0 in the second decimal place it is removing it. I notice it does this as soon as the string gets converted to a number.
This is how the input looks when first loaded. The preferred display would be 1.60 (two decimal places)
function ProductMaterial() {
const [name, setName] = useState("");
function handleChange(e) {
let val = e.target.value;
//ignore values entered after 2 decimal places
if (val.includes(".") && val.split(".")[1].length > 2) {
e.preventDefault();
} else {
setName(parseFloat(val));
setMaterialState(parseFloat(val));
}
calcTotal();
setTotal(product.id, product.total);
}
function handlePress(e) {
let val = e.target.value;
//ignore values entered after 2 decimal places.
//For some reason it needs to be compared to 1 for it to work with 0s.
if (val.includes(".") && val.split(".")[1].length > 1) {
e.preventDefault();
} else {
setName(parseFloat(val));
setMaterialState(parseFloat(val));
}
calcTotal();
setTotal(product.id, product.total);
}
return (
<div className="input-group-sm col-xs-2 input-group">
<div className="input-group-prepend">
<span className="input-group-text">$</span>
</div>
<input
className="form-control"
type="number"
min="0.00"
onChange={handleChange}
onKeyPress={handlePress}
value={product.material}
/>
</div>
);
}
Here is the code for setMaterialState. It changes the product.material value, and then calls setMaterial() in a useEffect() function. The setMaterial() takes the id of the product, and the new material value and saves it to state. Product.material needs to be a number for this for other calculations that happen.
function setMaterialState(newMaterial) {
product.material = newMaterial;
}
useEffect(() => {
setMaterial(product.id, product.material);
}, [product.material]);
I'm struggling on the forcing it to be two decimal places when there is a 0 at the end. I'm realizing that the issue with not starting doing 1.60 is due to it being converted to a float value and not being a string. I tried doing a UseEffect() [], but it seems like the only way to force the two decimal places is keeping it as a string or converting it to a string by using product.material.toFixed(2). Right now value in the input value={product.material} needs to be a number to correctly execute calculations and collect user input. I'm not sure what the best solution for this is.
Using the Intl.NumberFormat object is one approach you could try.
function ExampleComponent() {
const [inputVal, setInputVal] = useState("0.00");
// you can `parseFloat` the string back into a number
// where needed (e.g., calculations)
function handleChange(e) {
let number = e.target.value;
setInputVal(number);
}
// applies formatting when input has lost focus
function formatValue(e) {
if (inputVal) {
const locale = "en-US";
setInputVal(
new Intl.NumberFormat(locale, {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
useGrouping: false, // commas will break `parseFloat`
}).format(parseFloat(inputVal))
);
}
}
return (
<>
<p>${inputVal ? inputVal : "0.00"}</p>
<input
type="number"
min="0"
step="0.01"
onChange={handleChange}
onBlur={formatValue}
value={inputVal}
/>
</>
);
}
Link to a working example:
codesandbox.io/s/xenodochial-hypatia-omsmzi
First things first here is the code pen of the project;
https://codepen.io/furkancodes-the-typescripter/pen/jOyGJvx
I have tried searching for "." through contains() also with a condition to make it disabled but it does not work as I want it to.
decimal.addEventListener('click', (e) => {
decimal = e.target.value;
if (display.innerHTML === '') {
result = display.innerHTML = display.innerHTML.concat('0.');
} else if (display.innerHTML === output && display.innerHTML.indexOf(".") < 0) {
display.innerHTML = display.innerHTML.concat('.');
}
});
Also tried to come up with a solution like above but I am sure I am failing to grasp something here. Can anyone let me know what is wrong and lead me to the correct path to "prevent my calculator allowing more than one decimal" and any other improvements I can make.
Sample input:
4...4
Expected output:
4.4 ( not more than one decimal into the calculator display)
In order to stop the user from entering more than one decimal point for a single input, you can implement a counter.
let decimalCount = 0;
For the click handler for .number buttons add the following check.
// For the decimal increment decimal counter
if (number.id === ".") {
decimalCount++;
}
// For more than one decimal don't do anything. Return
if (number.id === "." && decimalCount > 1) {
return;
}
When the user enters another input (.operator click handler) reset the decimal counter to decimalCount = 0;.
Link: https://codesandbox.io/s/calculator-no-multiple-decimals-2fjnm
The write method to use for string is includes.
Using it solves the problem
decimal.addEventListener('click', function(){
if(display.innerHTML.includes('.') || firstNumber.innerHTML.includes('.')){
decimal.disabled = true;
} else {
decimal.disabled = false;
}
})
I have a state as value: 10.00 and once I update it with some operation and add it to a <Text> the ".00" part gets trimmed off. If it was a value like 10.50, it'll be displayed as 10.5
This is a issue as I want to display currency values. How to handle this?
Found the answer. To have the value with decimal values, use toFixed() method.
Example:
var value = 10;
value = value.toFixed(2);
this.setState({subTotal: value});
The output would be: 10.00
here is another solution you can also try, what i need is don't allow to enter more than 2 decimal digits (after decimal point) and also shouldn't allow more than two decimal points or any other character.
ConTwoDecDigit=(digit)=>{
return digit.indexOf(".")>0?
digit.split(".").length>=2?
digit.split(".")[0]+"."+digit.split(".")[1].substring(-1,2)
: digit
: digit
}
<TextInput
value={this.state.salary}
onChangeText={value => this.setState({ salary: this.ConTwoDecDigit(value) })}
keyboardType={'decimal-pad'}
/>
An alternative to the verified answer which catches more edge cases and allows string inputs. (defaults to 2dp but can be set by function caller)
export function normaliseValue (value: string, decimals = 2) {
if (!value) {
return ''
}
if (value === '.') {
return value = '0.'
}
var regex = new RegExp(`^-?\\d+(?:\\.\\d{0,${decimals}})?`)
const decimalsNumber = value.toString().match(regex)[0]
const parsed = parseFloat(decimalsNumber).toFixed(2)
if (isNaN(parsed)) {
return '0'
}
return parsed
}
Example use in code:
<TextInput
label='Hours worked'
placeholder='Hours worked'
keyboardType='decimal-pad'
value={String(values.hours)}
onChangeText={(val) => setFieldValue('hours', normaliseValue(val, 3))}
/>
I called a class called test for my textbox. When I entered the first value for e.g. the first value as 4., then suddenly the output coming as 4.00. I just want to restrict entry only for two decimal places.
$(".test").keyup(function (event) {
debugger;
this.value = parseFloat(this.value).toFixed(2);
});
This small change to your code may suffice:
this.value = this.value.replace (/(\.\d\d)\d+|([\d.]*)[^\d.]/, '$1$2');
Essentially replace the decimal point followed by any number of digits by a decimal point and the first two digits only. Or if a non digit is entered removes it.
What about something like this:
$(".test").keyup(function (event) {
if ((pointPos = this.value.indexOf('.')) >= 0)
$(this).attr("maxLength", pointPos+3);
else
$(this).removeAttr("maxLength");
});
Here is a working fiddle.
you can use the maxLength attribute for that, try
$(".test").keyup(function (event) {
var last = $(this).val()[$(this).val().length - 1];
if (last == '.') {
$(".test").attr("maxlength", $(this).val().length+2);
}
});
You shouldn't worry about what the user has in the input until they submit the form. You really don't care what's in there before then. However, if you want to warn about invalid input, you can put a message on the screen if you detect non–conforming input, e.g.
<script>
function validate(element) {
var re = /^\s*\d*\.?\d{0,2}\s*$/;
var errMsg = "Number must have a maximum of 2 decimal places";
var errNode = document.getElementById(element.name + '-error')
if (errNode) {
errNode.innerHTML = re.test(element.value)? '' : errMsg;
}
}
</script>
You should probably also put a listener on the change handler too to account for values that get there by other means.
$(document).on("keyup", ".ctc", function ()
{
if (!this.value.match(/^\s*\d*\.?\d{0,2}\s*$/) && this.value != "") {
this.value = "";
this.focus();
alert("Please Enter only alphabets in text");
}
});