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} />
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 would like to be able to store some form of dynamic structure in the state.
Each input element calls the same function "handleFormInput".
in "name" the input name is stored.
In "value" the actual value.
After each change these values are stored in the state.
If the user now clicks on the form button, the function "handleForm" is called. This function checks the state and reacts accordingly.
My problem: I check the length of the array, which is always 0.
If I output everything via Console.log, however, I see the elements in the array, also correctly. What have I not considered?
Called on every input change made
handleFormInput(event){
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.state.saveFormData[name] = value;
this.setState({
saveFormData : this.state.saveFormData
}, () => {console.log(this.state.saveFormData)});
}
Called when "Submitting"
handleForm(){
var l = 0;
this.state.saveFormData.forEach(element => {
l++;
});
console.log(l); // output: 0
if(this.state.saveFormData.length == 0){
this.setState({ openNew: false })
console.log(this.state.saveFormData);
}else{
console.log(this.state.saveFormData);
alert('we found Data' + JSON.stringify(this.state.saveFormData));
}
}
Ouput of console.log
Array []
length: 0
ort: "asd"
<prototype>: Array []
Working Example in a Nutshell
if you start with the input, the array is written to the state after each input. Stack Snippet already shows an empty array here. The console in the browser shows an array with one element but with a length of 0.
In the similar answers I find, it always has to do with the fact that the console does not map the exact moment of the array, but only shows a reference, and retrieves the data just in time when it is unfolded.
in my case it seems to behave differently. Since also in the console the array length of 0 is shown
function App() {
return ( < DataEditor / > );
}
class DataEditor extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
def: [],
defRaw: [],
loaded: false,
isLoading: false,
openNew: false,
editFormLoaded: false,
editFormData: [],
saveFormData: []
}
}
handleFormInput(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.state.saveFormData[name] = value;
this.setState({
saveFormData: this.state.saveFormData
});
}
handleForm() {
console.log("The Array Length is propably zero but we can access the element")
console.log(this.state.saveFormData.ort);
console.log("Log the testet Array")
console.log(this.state.saveFormData);
if (this.state.saveFormData.length == 0) {
this.setState({
openNew: false
})
console.log(this.state.saveFormData);
} else {
console.log(this.state.saveFormData);
alert('we found Data' + JSON.stringify(this.state.saveFormData));
}
}
render() {
return ( <
div >
<
input type = "text"
name = "ort"
onChange = {
(e) => this.handleFormInput(e)
}
/> <
button type = "button"
onClick = {
() => this.handleForm()
} > Test State Access and Array < /button> < /
div >
);
}
}
ReactDOM.render( < App / > , document.getElementById("root"));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
This has nothing to do with react, it's simply that on the inside array is still an instance of an object and can have it's properties modified the same way, without actually adding them to the iterable options.
Example here:
const array = [];
array.ort = "test";
console.log(array); // []
console.log(array.length); // 0
console.log(array.ort); // "test"
I suggest using an object instead and iterating over it's values with Object.values or Object.entries.
const object = {};
object.ort = "test";
console.log(object); // {ort: "test"}
console.log(Object.values(object).length); // 1
console.log(object.ort); // "test"
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.
I have many same inputs for percentage purposes, each of them working normally if I'm using normal numbers data. If I'm entering wrong erroneous data like letters or other chars its changing to 0. But it is changing only in screen, data actually catching wrong. For example if I will type 0fsdfsd into input result in screen will be 0 but actual data in input will be 0f.
How to save actual data as0 but not as 0f?
I'm using intl to format data as decimal, and isNaN to catch NaN values.
Input in render()
<input
name="bonusCategory"
value={this.toCurrency(category.bonusrate)}
className="form-control"
style={{ width: "60px" }}
type="text"
onChange={this.inputChanged.bind(this, idx, category.value)}
/>
/>
toCurrency()
toCurrency(number) {
const formatter = new Intl.NumberFormat("ru-RU", {
style: "decimal"
});
let newValue = isNaN(number) ? 0 : number;
return formatter.format(newValue);
}
inputChanged()
inputChanged = (idx, index, e) => {
const { categories } = this.state;
let bonusCategory = e.target.value;
console.log(bonusCategory);
if (bonusCategory.length > 3) return;
categories.forEach(category => {
category.bonusrate =
category.value === index ? e.target.value : category.bonusrate;
});
this.setState(oldState => {
const newDisabledButtons = [...oldState.disabledButtons];
newDisabledButtons[idx] = false;
return {
categories,
isEdit: false,
inCart: true,
disabledButtons: newDisabledButtons
};
});
};
in console.log I can see changing data of bonusCategory and it shows wrong result.
pen: https://codepen.io/fatdrfrog/pen/rNBXwrW
If you want to use some parameters not event for the onChange handler, please try this.
inputChanged = (idx, categoryvalue) => (e) => {
... ... ...
};
You can go ahead and allow the toCurrency to for visual effect but change the input change function as follows
inputChanged = (idx, index, e) => {
const { categories } = this.state;
//check for the value attribute and either set it to zero or to actual value
let bonusCategory = isNaN(e.target.value) ? 0 : e.target.value;
console.log(bonusCategory);
if (bonusCategory > 999) return;
categories.forEach(category => {
category.bonusrate =
category.value === index ? bonusCategory : category.bonusrate; //note the use of bunus category
});
this.setState(oldState => {
const newDisabledButtons = [...oldState.disabledButtons];
newDisabledButtons[idx] = false;
return {
categories,
isEdit: false,
inCart: true,
disabledButtons: newDisabledButtons
};
});
};
You can either use input with type='number' (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input/number) or parse user input before saving it to category.bonusrate (use parseInt or parseFloat depending on your number format):
category.bonusrate = category.value === index ? parseFloat(e.target.value) : category.bonusrate;
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