Why setState doesn't stack like the one before? - javascript

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('');
}

Related

React useState, setState and {state} in return

I come across the rendering issue with React State.
The problem is that {state} in return get value one beat late.
But the console log in handleChange shows right value.
If the previous value of state is 9, current value of state value is 10 then the console.log({state}) in handleChange shows 10 and the <span>{state}<span> in return shows 9.
It looks different from other state async problem.
I can't understand why this happened.
const [findText, setFindText] = useState("");
const [findCount, setFindCount] = useState(0);
const handleChange = (e) => {
let str = e.target.value;
setFindText(str);
let cnt = 0;
doxDocument.map((docx) => {
cnt += docx.src.split(findText).length - 1;
});
setFindCount(cnt);
console.log({findCount})
};
return(
<div>
<input
type="text"
value={findText}
onChange={handleChange}
/>
<span>{findCount} found <span>
</div>
);
Two problems...
findText will not have been updated to the new value when you use it in split(). Either use str instead or calculate findCount in a memo or effect hook with a dependency on findText.
You're completely misusing filter(). Use reduce() to calculate a computed sum
const [findText, setFindText] = useState("");
const findCount = useMemo(
() =>
findText
? doxDocument.reduce(
(sum, { src }) => sum + src.split(findText).length - 1,
0
)
: 0,
[findText, doxDocument] // may not need doxDocument
);
return (
<div id="App">
<input
type="text"
value={findText}
onChange={(e) => setFindText(e.target.value)}
/>
<span>{findCount} found</span>
</div>
);

Convert a Number or a String

I'm new to the "React" javascript framework to improve my skills, I gave myself as an exercise to convert a number or string into a symbol. For example a number will convert like this and a string will convert like this <?L> I present to you my code that I was able to write to reach the end of my exercise
import React from 'react';
import { useState } from 'react';
export default function Accueil() {
//submit
const ConvertResult = e => {
e.preventDefault();
}
//input
const [InputState, setInputState] = useState();
const InputContain = e => {
setInputState(e);
}
//button
const [ConvertInput, setConvertInput] = useState();
const ConvertData = () => {
if(typeof InputState === 'string'){
setConvertInput('?d')
}
}
return (
<div className="Accueil_Container">
<div className="Accueil_main">
<form onSubmit={e => ConvertResult(e)}>
<input type="text"
onInput={e => InputContain(e.target.value)}/> <br/>
<button onClick={ConvertData}>Convert</button>
</form>
</div>
<span>
{ConvertInput}
</span>
</div>
)
}
and here is the result I get in return
https://i.stack.imgur.com/AoRv0.png
characters such as <?d> or <?L> do not appear as many times as the string inserted in the input
if I could get some help from the more experienced or someone who has any idea how to fix this it will warm my heart.
I've been looking for a solution on the internet for 3 days, but nothing solves my problem or maybe I'm not typing the right keywords.
it seems like you set the variable "Inputstate" everytime you put in a new character but you dont give back the result every time, just once at the and.
how I roughly would put it into Code:
var Displaystring;
const InputContain = e => {
setInputState(e);
if(typeof InputState === 'string'){
Displaystring = Displaystring + '?d';
}
}
so you create a string that contains the converted data and add a symbol every time an input is registered in the input form. hope it made it a little clearer, sorry for the late answer

Matching multiple substrings to a string

I have an application where the user types in text, then this text is sent to the server and it returns an array of words that contain this text.
But as you can see the problem starts when there are more then 1 match. Here's my current code:
state.input !== '' && vocabularyItems && (vocabularyItems.map((vocabularyItem, index) => {
const regex = new RegExp(input, 'gi');
const results = vocabularyItem.matchAll(regex);
const tmp = [];
console.log(vocabularyItem);
for (const match of results) {
console.log(match);
let currentLoop = vocabularyItem.slice(0, match.index);
currentLoop += '<strong className="tt-highlight">';
currentLoop += vocabularyItem.slice(match.index, match.index + input.length);
currentLoop += '</strong>';
currentLoop += vocabularyItem.slice(match.index + input.length, vocabularyItem.length);
tmp.push(currentLoop);
}
console.table(tmp);
return (
<div
id={index}
className={`override-strong tt-suggestion tt-selectable ${cursor === index && 'tt-cursor'}`}
onMouseDown={handleClick}
key={index}
dangerouslySetInnerHTML={{ __html: tmp }}
/>
);
}))
and here are some examples in HTML code
1.
<strong className="tt-highlight">En</strong>kelkind
2.
<strong className="tt-highlight">En</strong>gagement
Engagem<strong className="tt-highlight">en</strong>t
as you can see, it works when there is only one occurence, but duplicates the word when more then one match is present. How can I end up with just something like
<strong>en</strong>gagem<strong>en</strong>t?
engagement?
I forgot to add that I need the case preserved
First off, I would recommend to use, something, like:
const results = vocabularyItems.filter(word => word.toLowerCase().includes(input.toLowerCase()))
for case insensitive vocabulary lookup.
Next, I would highlight the match in a bit different way. I'd split suggested option into parts (matching search input and non-matching ones) , then style those respectively:
const parts = suggestion.split(new RegExp(`(?=${match})|(?<=${match})`, 'gi'))
...
parts.map((part,key) => {
const style = part.toLowerCase() == match.toLowerCase() ? 'bold' : 'normal'
return <span style={{fontWeight:style}} {...{key}}>{part}</span>
})
I think it's safe enough to assume that you build autocomplete search input, thus you might find of use the quick demo below (excluding all the styling):
//dependencies
const { render } = ReactDOM,
{ useState } = React
//vocabulary
const vocabulary = ['engagement', 'Bentley', 'English', 'seven', 'Engagement']
//suggested option component
const SuggestedOption = ({suggestion, match}) => {
const parts = suggestion.split(new RegExp(`(?=${match})|(?<=${match})`, 'gi'))
return (
<div>
{
parts.map((part,key) => {
const style = part.toLowerCase() == match.toLowerCase() ? 'bold' : 'normal'
return <span style={{fontWeight:style}} {...{key}}>{part}</span>
})
}
</div>
)
}
//autocomplete component
const SearchBar = () => {
const [suggestions, setSuggestions] = useState([]),
[inputValue, setInputValue] = useState(''),
onInput = input => {
setInputValue(input)
setSuggestions(vocabulary.filter(word => input.length && word.toLowerCase().includes(input.toLowerCase())))
}
return (
<div>
<input onKeyUp={e => onInput(e.target.value)} />
<div >
{
suggestions.map((suggestion,key) => <SuggestedOption {...{key,suggestion,match:inputValue}} />)
}
</div>
</div>
)
}
render(
<SearchBar />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
Here's a way to do it with string.replace:
const list = [
'end',
'England',
'engagement'
]
const boldify = (search) => {
return (string) =>
string.replace(new RegExp(search, 'gi'), '<strong>$&</strong>')
}
document.body.innerHTML = list.map(boldify('en')).join('<br>')
EDIT: After some times thinking, and seeing the accepted answer, I wanted to push the vanilla JS version to have something more complete. And moreover, the React version seemed laggy to me, this one is faster!
Faster because:
it uses vanilla JavaScript
it uses correct dom manipulation (no innerHTML)
'use strict'
const list = [
'end',
'England',
'engagement',
'Ken Bogard',
'Venom',
'Engagement'
]
const boldify = (search) => {
return (string) => {
const div = document.createElement('div')
const parts = string.split(new RegExp(`(?=${search})|(?<=${search})`, 'gi'))
div.append(
...parts.map(part => {
if (part.toUpperCase() !== search.toUpperCase()) return part
const strong = document.createElement('strong')
strong.innerText = part
return strong
})
)
return div
}
}
const contains = (search) => {
search = search.toUpperCase()
return (string) => string.toUpperCase().indexOf(search) >= 0
}
const refreshSuggestions = () => {
// Fast removal of children.
while (suggestions.firstChild) suggestions.removeChild(suggestions.firstChild)
// nothing to do
if (searchInput.value.length == 0) return
const newSuggestions =
list.filter(contains(searchInput.value))
.map(boldify(searchInput.value))
suggestions.append(...newSuggestions)
}
searchInput.addEventListener('input', refreshSuggestions)
<input id="searchInput" list="suggestions">
<div id="suggestions">
</div>
EDIT - Yevgen's answer is much nicer than this one.
A simple loop can achieve this if i understand correctly what you want:
var array = ["end","engagement","Engagement","england","enough","not this","or this"];
function filterArray(array, id) {
var returnArray = [];
for (var i = 0; i < array.length; i++) {
value = array[i];
if (value.includes(id)) {
returnArray.push(value);
}
}
return returnArray;
}
var filteredArray = filterArray(array,"en");
console.log(filteredArray);
If you wanted to ensure duplicates (where case on Engagement causes a duplicate) you could set the string to lowercase before pushing to the array, and check the array for the existence of the string before pushing again.

Change Array Elements Only Three Times, Repeat

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});
}

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