I am trying to have a input field that does not allows user to type numeric value. (A non-numeric or a special character value will not be even allow to be typed).
One of the common approach on SO is the <input type="number" />. However, it suffers from two things:
1: The arrow: which I am able to get rid of following another stack post, which is not a problem.
2: The input field still allows negative sigh "-" as input.
I also tried: <input type="text" pattern="[0-9]*" onChange={this.handleInputToken}/>
handleInputToken(e){
console.log(e.currentTarget.value)
}
But this still allows non-numeric input
Is there an existing npm module or library that allows this simple implementation? Since it feels like a very common stuff with form data.
You can build a typed input component and have whatever logic.
This is very, very basic and not tested but will refuse to accept anything that does not coerce to a number and keep the old value, effectively silently dropping anything bad. it will still accept negative numbers, but you can extend the handleChange to fix it
class NumericInput extends React.Component {
state = {
value: ''
}
handleChange = e => {
const { onChange } = this.props;
const value = e.target.value.trim();
const num = Number(value);
let newValue = this.state.value;
if (!isNaN(num) && value !== '') {
newValue = num;
}
else if (value === ''){
newValue = value;
}
this.setState({
value: newValue
}, () => onChange && onChange(this.state.value));
}
render() {
const { onChange, ...rest } = this.props;
return <input {...rest} value={this.state.value} onChange={this.handleChange} />;
}
}
https://codesandbox.io/s/zq5ooml10l
obviously, you can use static getDerivedStateFromProps() to keep a value={} in sync from upstream and have it as a true 'controlled' component
Try something like this:
<input
type="number"
min="0"
value={this.state.number}
onChange={this.handleNumberChange}
/>
handleNumberChange(e) {
const asciiVal = e.target.value.charCodeAt(0);
if (
!(
(asciiVal > 95 && asciiVal < 106) ||
(asciiVal > 47 && asciiVal < 58) ||
asciiVal == 8
)
) {
return false;
}
this.setState({ number: e.target.value });
}
As soon as you try entering negative number, it will return false and the value won't be set in the input field
Related
I want to create an input box that allows to type only a distinct alphabet letter in the input box
( No duplicate alphabet value, ONLY ONE)
I looked up all the attributes for input box but I couldn't find one and there were no example.
Do I have to handle it within JavaScript functions?
(I am using React)
<input
className="App-Contain-Input"
name="containedLetter"
type={"text"}
onChange={containedChange}
/>
Here is how this can be done, note that we have to use onkeydown which fires when a key is being pressed, but not before it is being released (this way we can intercept and prevent the key stroke from being taken into account):
function MyInput() {
const containedChange = (event) => {
if (event.key.length === 1 && event.code.startsWith('Key') && event.code.length === 4 && event.target.value.indexOf(event.key) !== -1) {
event.preventDefault()
return false;
}
return true
}
return (
<input
id="input"
className="App-Contain-Input"
name="containedLetter"
type="text"
onkeydown={containedChange}
/>
}
A way to do it with a cheeky two-liner is to get rid of any non-letters with regex, then let Set do the de-duping by converting to Array => Set => Array => String:
As a side note, many of the other solutions posted here make one or both of two assumptions:
The user is only ever going to edit the last character... but what if they edit from the beginning or middle? Any last character solution will then fail.
The user will never copy/paste a complete value... again, if they do, most of these solutions will fail.
In general, it's best to be completely agnostic as to how the value is going to arrive to the input, and simply deal with the value after it has arrived.
import { useState } from 'react';
export default function Home() {
const [value, setValue] = useState('');
return (
<div>
<input
type='text'
value={value}
onChange={(e) => {
const onlyLetters = e.target.value.replace(/[^a-zA-Z]/g, '');
setValue(Array.from(new Set(onlyLetters.split(''))).join(''));
}}
/>
</div>
);
}
Your containedChange function can be like this:
const containedChange = (e) => {
const value = e.target.value;
const lastChar = value[value.length - 1];
if (value.slice(value.length - 1, value.length).indexOf(lastChar) != -1) {
// show error message
}
The function checks whether the last entered character already exists in the previous value or not.
An isogram is a word with no repeating letters. You can check this using regex
function isIsogram (str) {
return !/(.).*\1/.test(str);
}
Or using array.some
function isIsogram(str) {
var strArray = str.split("");
return !strArray.some(function(el,idx,arr){
return arr.lastIndexOf(el)!=idx;
});
}
In react - you need to use state to reject inputs which contain repeated letters (not an Isogram). Complete example on codesandbox.
import { useState } from 'react';
export default function App() {
const [ text, setText ] = useState('');
function isIsogram(str) {
var strArray = str.split("");
return !strArray.some(function(el,idx,arr){
return arr.lastIndexOf(el)!==idx;
});
}
function handleChange(e) {
const { value } = e.target;
console.log(isIsogram(value));
if (value.length === 1 || isIsogram(value)) {
setText(value);
}
}
return (
<input value={text} onChange={handleChange} />
);
}
Use a state to maintain the current value of the input. Then when the value changes get the last letter of the input, check that it's a letter, and if it is a letter and it isn't already in the state, update the state with the new input value.
const { useState } = React;
function Example() {
const [ input, setInput ] = useState('');
function handleChange(e) {
const { value } = e.target;
const lastChar = value.slice(-1);
const isLetter = /[a-zA-Z]/.test(lastChar);
if (isLetter && !input.includes(lastChar)) {
setInput(value);
}
}
function handleKeyDown(e) {
if (e.code === 'Backspace') {
setInput(input.slice(0, input.length - 1));
}
}
return (
<input
value={input}
onChange={handleChange}
onKeyDown={handleKeyDown}
/>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
One pure js solution would be to handle the input value in onkeyup event where we have access to the latest key as well as updated input target value.
If the latest key pressed is duplicate then we will found it on 2 location in the target value, first on some where middle of the target value and second at the end of the target value. So we can remove the value at the end (which is the duplicate one).
The below code snippet shows the implementation of above in React
const Input = () => {
const handleKeyUp = (e) => {
const { key, target: { value }} = e;
const len = value.length;
if (value.indexOf(key) < len - 2) {
e.target.value = value.slice(0, len - 1);
}
}
return (
<div>
<label>Unique Keywords</label>
<input type="text" placeholder="my-input" onKeyUp={handleKeyUp} />
</div>
);
}
I want to have an input number that allows any float between 0-1 including both whole numbers. But it's proving quite tricky just getting the backspace to actually delete a numbers as it comes as NaN
I got this:
const [rate, setRate] = useState<number>(0)
const addRate = (num: number) => {
if (typeof num !== 'number') {
setRate(0)
} else {
setRate(num)
}
}
...
<input
type='number'
value={rate}
onChange={e => addRate(parseFloat(e.target.value)}
/>
You can use achieve your goal with adding some conditions to the addRate state, also using step prop of input element:
import { useState } from "react";
export default function App() {
const [rate, setRate] = useState(0);
const addRate = (num) => {
if (typeof num !== "number" || isNaN(num) || num > 1) {
setRate(0);
} else {
setRate(num);
}
};
return (
<input
type="number"
value={rate === 0 ? "" : rate}
step={0.01}
onChange={(e) => addRate(parseFloat(e.target.value))}
/>
);
}
You can use on your input:
<input type='number' value={rate} min={0} max={1} step={0.000001}
onChange={e => addRate(parseFloat(e.target.value)} >
set the step the way you want to.
And for the NaN, try it:
if(typeof num == 'number' || isNaN(num))
let me know if it works!
If you are planning to work with inputs and numbers, to save you from a lot of headache (especially when using mobile devices to prompt user for a proper input type), fix errors caused by . vs ,, save time on developing code and getting cleaner code thanks to using directives I suggest you to check out some alternatives. It's not mine but man do I use this alot. https://digit-only.firebaseapp.com/demo I highly suggest this. Floting numbers and inputs are a pain in the...
If you don't want "yet another package" then you can ofcourse just inspect the code the get some ideas: https://github.com/changhuixu/ngx-digit-only/blob/main/projects/uiowa/digit-only/src/lib/digit-only.directive.ts
Hey I'm working on custom input component for integers only, with 2 modes based on acceptNegatives props, that indicates if it should accept the negative numbers or not, the problem is that my current implementation allows to add multiple - signs while the component should accept the negatives too.
What I'm trying to acomplish is to on prop acceptNegatives="true" the component should accept input or paste value like -2 or 2 not -0 or "--", "--2" because those values are treated as empty string for some reason.
Html of input component it uses default input from Buefy
<template>
<b-input
:placeholder="placeholder"
:value="value"
#input="handleChange"
#paste.native="handlePaste"
#keydown.native="handleKeyDown"
#mousewheel.native="$event.preventDefault()"
#blur="handleBlur"
:icon-right="iconRight"
:name="name"
autocomplete="off"
:type="type"
></b-input>
</template>
The JS part
Keydown event handler
handleKeyDown(event) {
const { key } = event;
if (this.acceptNegatives && this.isAdditionalKeyAllowed(event)) {
this.$emit("keydown", key);
return;
}
if (this.isAdditionalKeyAllowed(event) || this.isValidNumber(key)) {
return this.allowPositiveNumbersOnly(event);
}
event.preventDefault();
},
Is additional key
isAdditionalKeyAllowed(event) {
const allowedKeyWithoutMeta = Object.values(ALLOWED_KEYS).includes(keyCode);
if (this.hasAlreadyMinusSign(event.target.value, key)) {
return false;
}
if (this.acceptNegatives) {
return (
allowedKeyWithoutMeta || key === "-"
);
}
return allowedKeyWithoutMeta;
},
hasAleardyMinusSign fn to check if we already have minus sign, fails because - is treated as empty string
hasAlreadyMinusSign(value, key) {
return this.acceptNegatives && value.includes("-") && key === "-";
},
I've prepared also little demo how it works in Vue. Currently I have no idea how to resolve this. Should I use watcher or the value should be computed value somehow? Thanks for advice.
https://codesandbox.io/s/integernumberinput-j4jlj?file=/src/components/IntegerNumberInput/IntegerNumberInput.js
You need a way to strip the possibility of '------1', the easiest way to do this is using some regex:
inputVal() {
// Regex that replaces all occurrences of "-" except the last
return this.value.replace(/[-](?=.*[-])/g, "");
}
Then bind the input to this computed and use a getter and setter, something like this:
inputVal() {
get() {
// Regex that replaces all occurrences of "-" except the last
return this.value.replace(/[-](?=.*[-])/g, "");
},
set(val) {
this.value = val;
}
}
Or you could add the first snippet I've provided as a method that you call during some other validation process. That's up to you to decide
I have a dynamic form templated this way:
class betClient extends Component(
state = {
.............
}
................
const ServerConfigDetails = this.state.ServerConfigDetail.map(field => {
let SecretFields = "access_token" || "api_token" || "private_token" || "password" || "security_token" || "client_secret" || "refresh_token" || "oauth_client_id" || "oauth_client_secret" || "developer_token"
if (field.name === SecretFields) {
return <SecretInput inputlabel = {
field.name == SecretFields ? field.name : null
}
placeholder = {
field.placeholder
}
/>
}
if (field.name === "start_date") {
return <SelectDateInput inputlabel = "Start Date" / >
}
if (field.name === "end_date") {
return <SelectDateInput inputlabel = "End Date" / >
}
// Here we template all other fields that aren't date or secret field
if (field.name !== SecretFields) {
return <TextInputs inputlabel = {
field.label == null ? field.name : field.label
}
placeholder = {
field.placeholder
}
/>
}
})
render() {
return (
<div>
<Modal>
<form>
{ServerConfigDetails}
</form>
</Modal>
</div>
)
}
)
Everything works fine, the form is rendered dynamically but I somehow don't know how to get the form inputs preferably as an object into a state without having to declare each field individually in the initial state. Any help with this?
Not sure if this is clear enough but I will gladly provide any extra information.
I would first suggest, we should decide how our state might look like -
{
selects: {
// With individual keys with default values
},
dynamic: {} // no key-value pairs at first
}
Then make all your components - controlled - you control what to set by passing a value prop (and finally passing to your native inputs)
const { selects, dynamic } = this.state;
// pass a value prop as selects.<key-name> or empty string
And finally add onChange handlers for both your select and dynamic input field components. (and finally passing to your native inputs)
onChangeSelectField (event, fieldName) {}
//Same for InputField
These handlers will handle state such that keys are added if not present with value from the event target; add replace (overwrite) current key-value if a new value is entered.
{
const currentDynamicData = this.state.dynamic;
const newData = { ... currentDynamicData, {... updatedData}}; // updatedData is newer key-value pair
this.setState({ dynamic: newData });
}
This one is way to go about it.
The reason why I use onKeyDown is the lang for typing is Korean.
I have several inputs and when users type over than 20 bytes, I want to stop them to keep typing.
//this calculates bytes
const getByteLength = (s,b,i,c) => {
for(b=i=0;c=s.charCodeAt(i++);b+=c>>11?3:c>>7?2:1);
return b;
}
.
handleLength = (e) => {
let currentBytes = getByteLength(e.currentTarget.value);
// checked : it calculates bytes successfully.
if (currentBytes > 20){
// checked : This is only working for English. When I type over than 20 bytes, this removes the lastest letter. but I still can type Korean.
e.currentTarget.value =
e.currentTarget.value.substring(0,e.currentTarget.value.length - 1);
}
}
<input onKeyDown={this.handleLength}>
You should save the value of your input in a state variable:
class Foo extends Component {
state = {
value: ''
};
handleLength = (e) => {
let currentBytes = getByteLength(e.currentTarget.value);
// checked : it calculates bytes successfully.
let value = e.currentTarget.value;
if (currentBytes > 20){
value = e.currentTarget.value
.substring(0,e.currentTarget.value.length - 1);
}
this.setState({ value });
}
And then use the value in the state, in your input:
<input value={this.state.value} onKeyDown={this.handleLength} />