Number with decimal input in React? - javascript

I have a number input within a React component, and it needs to accept numbers with a decimal point. Usually, entries will be in the fractions of a cent, like 0.0073, that kind of thing.
<div className="form-group">
<label htmlFor="rate" className="col-sm-6 control-label">Rate:</label>
<div className="col-sm-2">
<input type="number"
title="Rate"
id="rate"
className="form-control"
value={this.props.rate}
min="0.00"
step="0.001"
max="1.00"
onChange={()=>{
console.log('page rate changed');
this.props.setrate($('#rate').val());
}} />
</div>
</div>
The issue is that with every keystroke, it's resetting the rate for the app, and then putting that value into the input. So it goes like this:
User types 0, the value is set to 0, and 0 is displayed.
User types ., 0. isn't a valid number, so the input is cleared.
Can anyone think of a workaround? I know I could just use a normal input, but type="number" leads to some nice stuff in various browsers.

<input
type="text"
value={this.props.rate}
onChange={this.onAmountChange}
/>
type should be text and input value should be defined by regex.
onAmountChange = e => {
const amount = e.target.value;
if (!amount || amount.match(/^\d{1,}(\.\d{0,4})?$/)) {
this.setState(() => ({ amount }));
}
};
regex here means: start with a number and add as many as you want. then optionally end with decimal numbers up to 4 decimals.

You can do something like this
const floatRegExp = new RegExp('^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$')
const handleValidationOnChange = (e, v, onChange) => {
const { value } = v
if (value === '' || floatRegExp.test(value)) {
onChange(e, v)
}
}
const InputFloat = props => {
if (typeof props.onChange !== 'function') {
return <Form.Input { ...props } />
}
const { onChange, ...parentProps } = props
return <Form.Input
{ ...parentProps }
onChange={(e, v) => handleValidationOnChange(e, v, onChange)}
/>
}
Form.Input can be any Component that has a value.
You will have to later check for '', that is unavoidable.

<input type="number"
title="Rate"
id="rate"
className="form-control"
value={this.props.rate}
min="0.00"
step="0.001"
max="1.00"
presicion={2} //very important
onChange={()=>{
console.log('page rate changed');
this.props.setrate($('#rate').val());
}} />

{(text) => this.setState({ value: text.replace( /^[-+]?[0-9]+\.[^0-9]+$/, ''), })}
This will replace any input on runtime if there is any input except decimal number

I had a similar issue where I had decimal numbers in a database and reading them into React. By default, the decimal would show up with trailing zeroes when displaying in React. Trying to display without the trailing zeroes gave me the issue that you describe above. My issue may be slightly different from what you are looking for, but I think it would help others. What I had to do:
Pull the data from the database with the desired formatting. For example: instead of select mynumber from mytable I did select (TRIM(mynumber)+0) as mynumber from mytable. This automatically removes the trailing zeroes.
I then read these values into the React JS script and set in state. These become the value of the input fields.
Then rather than preventing users from entering a non-number value I simply change the background color of the input field to red if the value is not a number.
if (isNaN(mynumber)) {
bgcolor = "#fdd" //Add this to your input style
}
I found the red background above good enough for my purposes but you can also implement an additional check when the user clicks the save button.

I've tried your code and wasn't really affected by your issue, the value is indeed empty when you type the last dot, but the input is not reset.
The thing I changed from your implementation is that I get the input value from the onChange event rather than use jQuery.
<input onChange={e => this.props.setrate(e.target.value)} />
But I doubt your issue comes from that though.
What you could do is not to call your setrate function when you detect an ending dot in your string. Your state will not be modified until the user types a valid number, so one keystroke after the 0..
<input onChange={e => {
const str = e.target.value
if (str.charAt(str.length - 1) === '.') { return }
this.props.setrate(str)
}} />

Related

How to properly control data input?

There is a task to supervise input in . It is necessary to give the ability to enter only strings of numbers ([0-9]) into the entity input. At the same time, if something else is entered, then do not overwrite value and do not display incorrect input in the input. I can't find a solution for my case. Validity check ( target.validity.valid ) didn't work either because I have control over the minimum and maximum lengths. At the same time, I have a universal function for several inputs, but only the entity needs to be checked. Please tell me how to correctly implement the check for input [0-9] and so that nothing else can be entered.
The examples that are on the resource are not suitable because they do not take into account the control of the minimum-maximum length
Below is a shortened code example
const [inputState, setInputState] = useState({title : "", entity: ""})
const handleChangeInputValue = (event) => {
const { target } = event;
const { name, value } = target;
// Need to check for numbers
setInputState({ ...inputState, [name]: value });
};
<input
required
minLength={5}
type="text"
placeholder="Enter name"
name="title"
value={inputState.title}
onChange={handleChangeInputValue}
/>
<input
required
minLength={13}
maxLength={15}
type="text"
placeholder="Enter entity"
name="entity"
value={inputState.entity}
onChange={handleChangeInputValue}
/>
you can use HTML 5
<input type="number" name="someid" />
This will work only in HTML5 complaint browser. Make sure your html document's doctype is:
<!DOCTYPE html>
if(name==='entity' && !value.match(/^\d+$/)) {
return
}

React not triggering re-rendering of form inputs after state is updated via onChange

I am trying to build out a verification code page.
If I create an individual state for each input box, and then use the code below, it works appropriately.
<input type="number" value={inputOne} className={styles.codeInput} onChange={e => setInputOne(e.target.value}/>
However, I was trying to consolidate the state for all four of the input boxes, into one state object.
Now, when I type in a number, it moves on to the next input, but it never renders the value. In dev tools, I see the value flash like it updates, but it still stays as "value" and not "value="1"" for example.
However, if I do anything else to my code, like for example, change a p tag's text, then suddenly it updates and the inputs show the correct value.
I'm just trying to figure out what I'm doing wrong here.
Here's my current code.
import { useState } from 'react'
import styles from '../../styles/SASS/login.module.scss'
export default function Verify(params) {
const [verifCode, setVerifCode] = useState(['','','','']);
const inputHandler = (e, index) => {
// get event target value
let value = e.target.value;
// update state
let newState = verifCode;
newState[index] = value;
setVerifCode(newState);
// move focus to next input
if (e.target.nextSibling) {
e.target.nextSibling.focus()
} else {
// if at the last input, remove focus
e.target.blur();
}
}
return (
<div className={styles.verify}>
<p className={styles.title}>Verification</p>
<p className={styles.message}>Please enter the verification code we sent to your email / mobile phone.</p>
<div className={styles.form}>
<input type="number" value={verifCode[0]} className={styles.codeInput} onChange={e => inputHandler(e, 0)}/>
<input type="number" value={verifCode[1]} className={styles.codeInput} onChange={e => inputHandler(e, 1)}/>
<input type="number" value={verifCode[2]} className={styles.codeInput} onChange={e => inputHandler(e, 2)}/>
<input type="number" value={verifCode[3]} className={styles.codeInput} onChange={e => inputHandler(e, 3)}/>
</div>
<div className={styles.footer}>
<button>Verify Code</button>
</div>
</div>
)
};
I believe the problem lies in the following code
// update state
let newState = verifCode;
newState[index] = value;
setVerifCode(newState);
First line of the code just adds a pointer to the value verifCode.
You modify an element in that array, but newState is still the same variable verifCode. Even though the array elements have changed essentially it is still same variable (same reference).
Try something like:
// update state
const newState = [...verifCode]; // create a copy of the old verifCode, creating new array
newState[index] = value; // modify element
setVerifCode(newState); // set the new array to the state

How to limit multiple input fields (which use v-model) to only accept numbers in Vue.js (without repeating code for every input field)?

I have multiple input fields and I want to limit them to accept numbers only in Vue.js.
I want do disable user from typing any characters except digits from 0-9.
I already did that successfully by doing this(this solution copy-paste proof):
Code in Vue.js template:
<input type="text" name="priceMax" class="input" #input="correctNumberInput" />
Method that removes everything except numbers:
correctNumberInput: function(event){
event.target.value = event.target.value.replace(/[^0-9]/g, "");
}
This worked perfectly fine on multiple fields.
Here comes the problem: For different reason, I needed to use v-model on these input fields. After adding v-model my method doesn't work anymore. I guess it's because v-model also uses input event under the hood. So only adding "v-model", stops it from working:
<input type="text" name="priceMax" class="input" #input="correctNumberInput" v-model="priceMax" />
I have few possible solutions in mind, but all of them include a lot of repeated code.
For example, I could add watchers for every input field, but that would be a lot of repeated code (because I would need to do it for every input field). I have 5 input fields, so basically I would need to write 5 almost identical watchers. I would like to avoid that if that is possible... For example:
watch:{
number(){
this.priceMax = this.priceMax.replace(/[^0-9]/g, "");
}
}
Is there any way I can solve it and make it as simple as my solution was without repeating code? It would be nice to also have solution that is copy-paste proof. All suggestions are welcome! Thanks in advance!
I've tried to test some code. Here what I have (link to the example):
<template>
<div>
<div>
<input
type="text"
name="priceMin"
class="input"
v-model="priceMin"
#input="correctNumberInput"
>
<label v-html="priceMin"></label>
</div>
<div>
<input
type="text"
name="priceMax"
class="input"
v-model="priceMax"
#input="correctNumberInput"
>
<label v-html="priceMax"></label>
</div>
</div>
</template>
<script>
export default {
name: "MyInput",
data: () => {
return {
priceMin: "",
priceMax: ""
};
},
methods: {
correctNumberInput: function(event, data) {
const name = event.target.name;
let value = String(this[name]).replace(/[^0-9]/g, "");
if (value) {
this[name] = parseInt(value, 10);
} else {
this[name] = "";
}
}
}
};
</script>
<style scoped>
input {
border: 1px solid black;
}
</style>
This is the code:
correctNumberInput: function(event, data) {
const name = event.target.name;
let value = String(this[name]).replace(/[^0-9]/g, "");
if (value) {
this[name] = parseInt(value, 10);
} else {
this[name] = "";
}
}
So I used your function, but I am not changing the event.target.value, I am changing the data. So I need to know the name of that data, that's why I use name attribute from input fields (const name = event.target.name;)
Update
If we have input type=number, then it has strange (empty) value inside #input callback. So it seems, better use keyboard filter (example here):
The main idea to have keyboard filter:
filterDigitKeys: function(event) {
const code = window.Event ? event.which : event.keyCode;
const isSpecial =
code === 37 ||
code === 39 ||
code === 8 ||
code === 46 ||
code === 35 ||
code === 36;
const isDigit = code >= 48 && code <= 57;
const isKeypad = code >= 96 && code <= 105;
if (!isSpecial && !isDigit && !isKeypad) {
// if not number or special (arrows, delete, home, end)
event.preventDefault();
return false;
}
}
And attach it to inputs:
<input type="number" min="0" name="numberInput" class="input"
v-model.number="numberInput" #keydown="filterDigitKeys">
Note: if we keep only #keydown handler, then we will not filter text insert into our inputs (but ctrl+v is not working anyway, only by mouse).
Maybe you can try this:
<input type="number" name="priceMax" class="input" #input="correctNumberInput" v-model.number="priceMax" />
From that site: click.

Change input placeholder on global state change

I am in the mid of building a shopping cart demo, I am almost done for my purposes however I faced a frustrating bug that I can't seem to get around
The box in the middle is supposed to be an input field so that if a user requests a large number of products they can easily type it down instead of incrementing.
When I type in a number it reflects correctly and produces the desired change.
However, if I typed in a number and later used the (+ / -) buttons the placeholder value doesn't seem to change.
Here is my code
<div className="prod-count">
<button className="plus" onClick={()=> onIncrement(product.id)}>+</button>
<input type="text" onChange={(e)=> handleValue(product.id, valueExtracter(e))} className="num-box" placeholder={product.quantity}/>
<button className="minus" onClick={()=> onDecrement(product.id, product.quantity)}>-</button>
</div>
and here is the onChange function
const valueExtracter = (e) => {
return parseInt(e.target.value)
}
//handle value will only run if input is a number
const handleValue = (id, value) => {
if (!isNaN(value) && value > 0){
dispatch(setQuantity(id, value))
}
}
I am pretty sure the action gets dispatched correctly and I can see it in the total values, so the (product.quantity) value changes, but the placeholder doesn't get updated
One Last thing: The desired effect gets applied once I switched placeholder to value, however, once the component is created with the value of (1) the end user cannot erase this (1) and needs to select and overwrite it
I would just trade the placeholder for value.
EDIT: 'I would trade minus button position with the increment one' its seens more user friendly
<div className="prod-count">
<button className="minus" onClick={()=> onDecrement(product.id, product.quantity)}>-</button>
<input type="text" onChange={(e)=> handleValue(product.id, valueExtracter(e))} className="num-box" value={product.quantity}/>
<button className="plus" onClick={()=> onIncrement(product.id)}>+</button>
</div>
Looks like you're missing the value prop
<input
type="text"
onChange={(e)=> handleValue(product.id, valueExtracter(e))}
className="num-box"
value={product.quantity}
placeholder={product.quantity}
/>
Also, you may not need placeholder prop with value prop there.
Had to make the value extracter accept any thing and turn to an empty string and pass it to the handle value
const valueExtracter = (e) => {
let value = parseInt(e.target.value)
if (isNaN(value)) return ''
else return value
}
//handle value will only run if input is a number
const handleValue = (id, value) => {
if (value === '' || value > 0){
dispatch(setQuantity(id, value))
}
}
switched the placeholder to value since it can accept an empty string now
<input type="text" onChange={(e)=> handleValue(product.id, valueExtracter(e))} className="num-box" value={product.quantity}/>
if the reducer catches an empty string it'll multiply it by the price which would turn the displayed price to 0 so I made a fix for that too
<div className="prod-price">
{itemPrice >= product.price ? `$ ${itemPrice.toFixed(2)}` : <span className="disabled-price">$ {product.price}</span>}
</div>
the displayed price will display in red instead if the quantity is '' since '' * a number will return 0
and finally
the total quantity and price will be updated via the reducer and if the products quantity is '' it'll turn the totals to a string so I can error check on submition and alert the user to write a quantity or delete the item
so I wrote this in the reducer to get every thing back if the user decides to use the increment value once the field has beenn cleared "" + 1 = "1" , so this was fixed by checking in the educer function
case "UPDATE_CART":
const indexToUpdate = cartCopy.findIndex(product => product.id === action.id)
// if condition checks for when the item is set to '' to reset its value to 1
if (cartCopy[indexToUpdate].quantity === ''){
let itemReadded = {
...cartCopy[indexToUpdate],
quantity: 1
}
newCart = [
...cartCopy.slice(0,indexToUpdate),
itemReadded,
...cartCopy.slice(indexToUpdate + 1)
]
return {
...state,
cart: newCart ,
totalAmount: totals(newCart).amount,
totalQty: totals(newCart).qty,
}
}

Automated Form Filling

I’m looking for a way to automate a form.
Here are the details:
Extract a certain number (displayed in its html)
Do some calculations on the extracted number (percentage of that number)
Then automatically fill the remaining input fields with the result instead of typing it out.
This is a common occurrence in forms. The solution depends on what framework / libraries you're using. Assuming you're using none, here is how you might go about it:
https://jsfiddle.net/f52h1smj/1/
rough HTML:
<form>
<label for="number">Number: </label>
<input id="number" type="number" />
<br /> <br />
<label for="calculatedNumber">Calculated Number (50%): </label>
<input id="calculatedNumber" type="number" disabled="true" />
</form>
JS:
(() => {
//get the form element nodes
const $number = document.getElementById("number");
const $calculatedNumber = document.getElementById("calculatedNumber");
//add an event listen to the value you're going to use to pre calculate the other fields
$number.addEventListener("keyup", (e) => {
//it's value is available like so
const value = e.target.value;
//do some validation so that you're calculations don't throw exceptions
if (Number(value) !== 0 && !Number.isNaN(value)) {
//set the value of the other inputs to whatever you like by setting the 'value' property of the node.
$calculatedNumber.value = value / 2;
} else {
$calculatedNumber.value = null;
}
});
})();
These things become a lot simpler in frameworks like React and Angular.

Categories