Change Array Elements Only Three Times, Repeat - javascript

What is the best way to change each element of an array based on the length of the array?
For example:
User #1 input = "XYZVC"
Expected Output = "BLABL"
User #2 input = "XYZVCAD"
Expected Output = "BLABLAB"
I want B to replace index[0], L to replace index[1], and A to replace index[2]. The part I'm struggling with the most is also getting it to repeat if the user input is longer than 3.
My Attempts
I've attempted to split() the input into an array, shift() it and push() it into a new array without any luck.
I've pasted in my code below for even more detail:
import React from 'react'
class UserInput extends React.Component {
constructor(props) {
super(props);
this.state = {
value: ''
};
}
handleChange = (event) => {
this.setState({value: event.target.value})
}
handleSubmit = (event) => {
alert('Your input has been submitted: ' + this.state.value)
event.preventDefault()
}
badRobot = () => {
//Check length of user input
//Add B, L, A, matching the length of the user input
//Store it within a new variable
//Plug that into the bad robots output
let checkedInput = this.state.value
let checkedArray = checkedInput.split('')
const newArr = checkedInput.shift()
}
render(){
return(
<div>
<form onSubmit={this.handleSubmit}>
<label>
<p>Say Something</p>
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
<div>
<h3>Good Robot</h3>
<p>I hear you saying {this.state.value}. Is that correct?</p>
<h3>Bad Robot</h3>
<p>.</p>
<h3>Kanyebot 5000</h3>
<p>I'm gonna let you finish but Beyonce is {this.state.value}.</p>
</div>
</div>
)
}
}
export default UserInput

UPDATE
As it turns out, there is a string function, padStart (works with padEnd too) that will do exactly that pretty easily :
const input1 = "XYZVCAD"
const input2 = "erkjguherkluagheruhg"
function mask(str){
return ''.padStart(str.length, 'BLA');
}
console.log(mask(input1))
console.log(mask(input2))
One way to achieve it is to use the repeat function and then cut out your string depending on the original string length :
const input1 = "XYZVCAD"
const input2 = "erkjguherkluagheruhg"
function mask(str){
return 'BLA'.repeat(str.length / 3 + 1).substring(0, str.length)
}
console.log(mask(input1))
console.log(mask(input2))
The repeat function will repeat your string x times, as you will see in the docs linked above. Here, since your string is 3 characters long, we only need to repeat your string depending on the length of the original one, divided by 3. I am adding one since a division like 5/3 will be rounded to 1, leading to BLA even though your string is longer.
The last step, substring, will simply cut your string to the exact same length as the original one.
Here is another way of achieving it, by splitting your string into an array, and giving it the correct letter by using the modulus operator :
const input1 = "XYZVCAD"
const input2 = "erkjguherkluagheruhg"
function mask(str){
return str.split('').map((ch, index) => 'BLA'[index % 3]).join('')
}
console.log(mask(input1))
console.log(mask(input2))

This isn't code, but it's the approach I would take:
Split the input into a char array
For loop through it, with index starting at 0
If index + 1 MOD 3 = 0, set value to 'A' (array[i+1]%3 === 0)
If index + 1 MOD 2 = 0, set value to 'L'
Otherwise set value to 'B'
Return a string of the joined array items (array.join)

var myString = "somestringofsomelength";
var replacementsArr=["B","L","A"];
function replacer(str){
repString="";
for(var i=0; i<str.length;i++){
repString+=replacementsArr[i % replacementsArr.length];
}
return repString;
}
console.log(replacer(myString));
That's the way I would do it. It's using remainder division to partition the string from being 12345678 in index relative to replacementsArr to 12312312

You could change it straight away
const letters = ['B','L','A'];
handleChange = (event) => {
const length = event.target.value.length;
const letter = letters[length%3];
this.setState({value: this.state.value+letter});
}

Related

Caret Jumping to End When Rejecting Input

I have an input for a value. I want to validate the new value entered by the user and prevent the value from appearing in the input if it's invalid. For instance, the value should be numeric, and hence can only contain numbers, dots, and commas.
I have a validation function which returns true/false depending on whether a value meets the criteria (works as intended):
function validateInput(input) {
let allow = true
const splitValuesByDecimal = input.split(',')
if(allow) allow = /^[\d|,|.]*?$/.test(input) // allow only digits, dots, and commas
if(allow) allow = splitValuesByDecimal.length <= 2 // allow only 1 decimal separator (comma)
if(allow && splitValuesByDecimal.length > 1) allow = splitValuesByDecimal.at(-1).length <= 2 // allow only maximum of 2 decimal characters
return allow
}
In my input field, I call a custom handleChange function which ensures that the new input is valid before storing it in the state variable.
Rendering input
<input value={inputVal} onChange={handleChange} />
Handling input change
function handleChange(e) {
const value = e.target.value
if(validateInput(value)) {
setInputVal(value)
}
}
If an invalid input is inserted and validation fails, the value in my input field does not change (as intended), but the caret jumps to the end of the input field (not desirable).
Attempted Solution
I have tried to fix the caret jumping behaviour by adopting this answer where I created a cursor state variable which keeps track of the intended caret position:
function MyComponent() {
const [inputVal, setInputVal] = useState('')
const [cursor, setCursor] = useState(null)
const inputRef = useRef()
useEffect(() => {
if (inputRef.current) inputRef.current.setSelectionRange(cursor, cursor);
}, [inputRef, cursor, inputVal])
function validateInput(input) {
...
}
function handleChange(e) {
const value = e.target.value
if(validateInput(value)) {
setCursor(e.target.selectionStart)
setInputVal(value)
}
else {
setCursor(e.target.selectionStart - 1)
}
}
return (
<input
ref={inputRef}
value={inputVal}
onChange={handleChange} />
)
}
With this solution, the caret persists at the correct position the first time an invalid value is entered, but as I keep on subsequently pressing the same key inserting invalid value over and over again, after the first time, the caret moves back to the end of the input. This is because the useEffect is not called the second and subsequent times.
How can I make this work so that caret always stays at the same position regardless of how many times the user attempts to insert an invalid value?
Update :-
Sorry my bad, I haven't looked at the question completely. However what I was saying is also correct but not in current case. Please find the updated code below. Below code should also be more performant as it does not involve re-renders on every change
import React, { useState, useRef } from 'react';
export function App(props) {
const inputRef = useRef()
const oldVal = useRef(null)
function validateInput(input) {
let allow = true
const splitValuesByDecimal = input.split(',')
if(allow) allow = /^[\d|,|.]*?$/.test(input) // allow only digits, dots, and commas
if(allow) allow = splitValuesByDecimal.length <= 2 // allow only 1 decimal separator (comma)
if(allow && splitValuesByDecimal.length > 1) allow = splitValuesByDecimal.at(-1).length <= 2 // allow only maximum of 2 decimal characters
console.log(allow)
return allow
}
function handleChange(e) {
const valueNew = e.target.value
const pos = e.target.selectionStart - 1
if(validateInput(valueNew)) {
oldVal.current = valueNew
} else {
inputRef.current.value = oldVal.current
inputRef.current.setSelectionRange(pos, pos)
}
}
return (
<div key='mydiv' className='App'>
<input key='MyFixedKeyValue' ref={inputRef} onChange={handleChange} />
</div>
);
}
I also changed the same on playcode code link
React re-renders the whole component on setstate call. In your case values are being kept as it is a controlled input. But behind the scene react is basically creating a new input field every time you are entering a value. To force react to use the same field pass key prop with constant value.
<input
key='MyFixedKeyValue'
ref={inputRef}
value={inputVal}
onChange={handleChange} />

Why setState doesn't stack like the one before?

I have a problem. I try to convert my string dynamically, but for some reason, it only converts the first letter of my initial string. What can I do to solve this?
Ex:
input: Mike
String_1 = 'Mike'
String_2 = 13 (I want it to be 139115, 13 for M, 9 for I, 11 for k and 5 for e).
This is my code:
import './App.css';
import React, {useState} from 'react';
import emojies from './emojies';
function App() {
let [String_1, setString_1] = useState( '' );
let [String_2, setString_2] = useState( '' );
let [Index, setIndex] = useState();
return (
<div>
<div>
<input
type="text"
value={String_1}
placeholder="Enter a message"
onChange={e => {
const val = e.target.value;
setString_1(val);
setString_2(val.toLowerCase().charCodeAt(0)-96);
}
}
/>
</div>
<div>
<p>{String_1}</p>
<p>{String_2}</p>
</div>
</div>
);
}
export default App;
This has nothing to do with React or setState.
Your issue is the logic around generating that String_2.
As you can see from the below snippet, your val.toLowerCase().charCodeAt(0)-96 only returns 13, so setState is acting correctly by passing on that value.
const val = 'Mike'
const String_2 = val.toLowerCase().charCodeAt(0) - 96
console.log(String_2)
const correct = parseInt(val.toLowerCase().split('').map(x => x.charCodeAt() - 96).join(''))
console.log(correct)
The new logic splits the string up into chars, maps over each of them to create a list of chars, then joins them together and converts into a int.
You make a mistake on setting the value for String_2.
Try this.
setString_2(val.split("").map(c => {
return c.toLowerCase().charCodeAt(0) - 96;
}).join(""));
Pass your string value from input box to this function. It iterates over all the alphabets from the string and convert them to their idx + 1 and join everything.
const convertStringToCharIdxString = (str) => {
const calcCharIdx = (char) => char.charCodeAt(0) - 97 + 1; // + 1, because you are considering a as 1
return str.split('').map(calcCharIdx).join('');
}

How to make specific number of characters change color?

I'm making a Karuta web game.
In karuta, there are "kimariji", which are the minimum amount of "letters" needed to know which card to take. These counts will be stored in the API/JSON I've created, which get saved as a state and read from.
I'm looking to do this: (english example)
var kimarijiCount = 6
string = 'Elephant'
in this case, only 'Elepha' turns red.
var kimarijiCount = 2
string = 'Rhino'
in this case, only 'Rh' turns red.
How can I do this using javascript/react? I'm assuming my pseudocode would be:
//get string
//get count variable x
//set css for the first x chars to a different colour
This is how my code for creating the cards current looks, if it helps
render() {
if (this.props.displayType === "kanji") {
return (
<div id="CardContainer" className="cardContainer">
{this.props.cards.slice(0, this.props.number).map((cards, index) => (
<div id="card" className="cardOuter" onClick={() => this.props.handleComp(index)}>
<div className="cardInner">
<p key={index}>{cards.back}</p>
</div>
</div>
))}
</div>)
}
Here's a sandbox that should work for the example you gave!
In short, you probably want to use slice on your kimariji:
// Slice the first `kimarjiCount` characters of kimarji and make them red
<span color={{ color: "red" }}>{kimarji?.slice(0, kimarjiCount)}</span>
// Append the rest of the kimarji
<span>{kimarji?.slice(kimarjiCount, kimarji.length)}</span>
This uses the optional chaining operator to ensure that the kimarji variable exists before calling slice on it.
You can use const, let, or var for text_color, which helps you to customize and change color property.
// var kimarijiCount = 2
// var str = 'Rhino'
const text_color = { color: 'red' };
let res = str.substring(0, kimarijiCount);
let rest = str.substring(kimarijiCount, str.length);
<span style= { text_color }> { res } </span> { rest };
kimarijiCount must be limited to str.length

Simple react forEach not returning the correct value

I'm trying to understand why the following will not, for example, take input like joe bloggs and return 'Joe' (with an uppercase letter J) using a forEach function rather than a classic for loop (commented out).
I have the form as a simple component:
import React from 'react';
const SignupForm = () => {
const onSubmit = (e) => {
let name = e.target.closest('form').querySelector('input[name="name"]'),
nameVal = name.value;
// 1. Gets first name only if full name provided
nameVal = nameVal.split(' ')[0];
// 2. Capitalises name first letter only & lowercase the rest
nameVal = nameVal.split('');
/*for (let i = 0; i < nameVal.length; i++) {
if (i == 0) {
nameVal[i] = nameVal[i].toUpperCase();
} else {
nameVal[i] = nameVal[i].toLowerCase();
}
}*/
/* CODE BELOW HERE */
nameVal.forEach((cur, i) => {
console.log('cur', cur, 'i', i);
return i == 0 ? cur.toUpperCase() : cur.toLowerCase();
});
/* CODE ABOVE HERE */
console.log('nameVal now', nameVal);
nameVal = nameVal.join('');
name.value = nameVal;
console.log('test form submit', e.target, 'nameVal: ', nameVal);
};
return (
<div id='em-signup-form'>
<h2>Enter Details:</h2>
<form id='prospect-form'>
<label htmlFor='name'>Name:</label>
<input
type='text'
name='name'
placeholder='Name*'
required='required'
pattern='[a-zA-Z ]+'
oninvalid="setCustomValidity('Invalid Name')"
oninput="setCustomValidity('')"
/>
<label htmlFor='email'>Email Address:</label>
<input
type='text'
name='email'
placeholder='Email*'
required='required'
pattern='[a-z0-9._%+-]+#[a-z0-9.-]+\.[a-z]{2,}$'
oninvalid="setCustomValidity('Invalid Email')"
oninput="setCustomValidity('')"
/>
<input type='hidden' name='tag' value='randomTag' />
<button type='submit' className='btn-std' onClick={onSubmit}>
Add Subscriber
</button>
</form>
</div>
);
};
export default SignupForm;
Note the relevant part below:
nameVal.forEach((cur, i) => {
console.log('cur', cur, 'i', i);
return i == 0 ? cur.toUpperCase() : cur.toLowerCase();
});
I thought that nothing was actually being changed here and that this is skipped passed, hence I changed it to the below:
nameVal.forEach((cur, i) => {
console.log('cur', cur, 'i', i);
i == 0 ? (cur = cur.toUpperCase()) : (cur = cur.toLowerCase());
});
But still nothing. How can I get the code to work using a forEach function rather than a classic for loop? This should be seemingly simple although I'm still getting used to the ES6+ syntax rather than ES5.
I know there are also errors with oninvalid and oninput although I assume there's a better react approach to this - any ideas? Thanks for any help here
You need to use map in order to modify the current array. Difference between both, here
Actually, your main question is not about ReactJS, it is related to functional loop on an Array. Based on ES6+ the map function return a new array and doesn't mutate the given array. the forEach function just gets a callback function and run it over the given array.
So, for your case, you should use the map function like below:
const Array = [1, 2, 3, 4];
const newArray = Array.map(item => item + 2);
The newArray is now [3, 4, 5, 6].

formatting number in input react

I have made an Input component. If it is a number I want to format it correctly, like a currency. I.e. 4000 would be 4,000.
Here is a codesandbox.
I am having issues with displaying and updating this.
<Input initialValue={'400000000'} isNumber={true} />
My Input component looks like this.
type Props = {
initialValue?: string;
isNumber?: boolean;
};
const Input = ({ initialValue = '', isNumber }: Props) => {
const [value, updateValue] = useState(initialValue);
const update = (val: any) => {
if (isNumber) {
const x = Number(val);
updateValue(x.toLocaleString());
} else {
updateValue(val);
}
};
return (
<StyledInput
type="text"
value={value}
onChange={e => update(e.target.value)}
/>
);
};
I am seeing an error NaN in my input component. Anyone have any ideas?
Javascript has a number formatter (part of the Internationalization API).
// Quick Solution With Intl.NumberFormat
const update = (val: any) => {
var formatter = new Intl.NumberFormat("en-US"); // Intl language tag,
updateValue(formatter.format(val.replace(/,/g, ""))); //Remove ',' to format number again
};
Code Snippet:
// Intl.NumberFormat With React State Update
var currentVal = 0;
...
const update = (event: any) => {
/**
https://stackoverflow.com/questions/35535688/stop-cursor-jumping-when-formatting-number-in-react
https://github.com/facebook/react/issues/955
*/
const caret = event.target.selectionStart
const element = event.target
window.requestAnimationFrame(() => {
element.selectionStart = caret
element.selectionEnd = caret
})
// -- Stop cursor jumping when formatting number in React
var val = event.target.value.replace(/(\..*)\./g, '$1') //Replace Multiple Dot(.)
var x = Number(val.replace(/,/g, ""));
if (currentVal != x) {
var formatter = new Intl.NumberFormat("en-US", { minimumFractionDigits:2});
currentVal = formatter.format(x);
updateValue(currentVal);
}else{
updateValue(val);
}
};
return (<input type="text" value={value} onChange={e => update(e)} />);
Note : Code Snippet gives you an idea to format numbers, You need to handle few more use-cases for production.
Also check the react-number-format, Which may suit for your application.
Reference :
Intl.NumberFormat vs Number.prototype.toLocaleString
How can I format numbers as dollars currency string in
JavaScript?
Intl.NumberFormat | MDN
The problem is in
const x = Number(val);
when you evaluate Number("32,423,343"), a string including commas Js will throw an error...
The correct way would be sending the number without commas.. Number("32432343")
To solve it you can add this line to remove the commas, before evaluating to a Number..
val = val.replace(/,/g, '');
https://codesandbox.io/s/r0vm8nxvjo

Categories

Resources