If we see the code, a word is always repeated (name, surname, position, nation).
const nameInput = document.getElementById('name-personalized')
let nameInputValue
const surnameInput = document.getElementById('surname-personalized')
let surnameInputValue
const positionInput = document.getElementById('position-personalized')
let positionInputValue
const nationInput = document.getElementById('nation-personalized')
let nationInputValue
nameInput.addEventListener('input', (event) => {
let value = event.target.value
value = value.trim().toUpperCase()
nameInputValue = value
})
surnameInput.addEventListener('input', (event) => {
let value = event.target.value
value = value.trim().toUpperCase()
surnameInputValue = value
})
positionInput.addEventListener('input', (event) => {
let value = event.target.value
value = value.trim().toUpperCase()
positionInputValue = value
})
nationInput.addEventListener('input', (event) => {
let value = event.target.value
value = value.trim()
nationInputValue = value
})
I want to write just that word, example:
avoidRepeat(name)
And let's say using a for cycle but I don't know how to do that.
Here's one way. Just wrap all the repeated code up in a function and pass the specifics as argument functions. I have removed the globals and replaced them with callback functions.
I'd say this solution is not optimal as it decreases code flexibility for the sake of making the code shorter. Passing two functions to the same function isn't ideal for readability.
You may find libraries like jquery, rxjs or even functional libraries like ramda could help reduce repetition and keep flexibility and readability intact.
<html>
<head><title>whatever</title></head>
<body>
<label for="name-personalized">name-personalized: </label><input type="text" id="name-personalized"><br>
<label for="surname-personalized">surname-personalized: </label><input type="text" id="surname-personalized"><br>
<label for="position-personalized">position-personalized: </label><input type="text" id="position-personalized"><br>
<label for="nation-personalized">nation-personalized: </label><input type="text" id="nation-personalized">
</body>
<script>
function avoidRepeat(name, mapFn, cbFn) {
const el = document.getElementById(name)
el.addEventListener('input', (event) => {
const val = mapFn(event.target.value);
cbFn(val, event.target);
})
}
const fnTrim = s => s.trim()
const fnTrimUpper = s => s.trim().toUpperCase()
const fnLog = (val, el) => console.log('for', el.getAttribute('id'), 'the value is ' + val)
avoidRepeat('name-personalized', fnTrimUpper, fnLog)
avoidRepeat('surname-personalized', fnTrimUpper, fnLog)
avoidRepeat('position-personalized', fnTrimUpper, fnLog)
avoidRepeat('nation-personalized', fnTrim, fnLog)
</script>
</html>
You can store the data in an object via the input's ID and just add an event listener to the inputs. Then accessed in bracket notation. I also removed -personalized from the keys.
This code is all you need for all of those and even future inputs
var values = {};
const inputs = document.querySelectorAll("input");
inputs.forEach(function(el) {
el.addEventListener('input', (event) => {
values[event.target.getAttribute("id").replace("-personalized","")] = event.target.value.trim()
})
});
You can access the data like this
console.log(values["nation"]);
Related
When a user enters a word in a text input, it is displayed on my page. And so the names can accumulate.
At the same time, I want the names to be pushed into an array. Unfortunately, I can't do it:
function getinnerText() {
const arr = []
const newProduct = document.createElement("li");
newProduct.textContent = `${name}`;
document.querySelector(".nameArea").appendChild(newProduct)
arr.push(name)
}
It always creates me my array with only the last value added.
Does anyone have a solution? Thanks a lot =)!
You need to move your array outside of the function. Each time you call the function, you are recreating the array with only one item.
I would separate the model from the view. Each time you add a product to the array, re-render what is in the array.
const
nameInput = document.querySelector('input[name="product"]'),
addButton = document.querySelector('#add'),
nameArea = document.querySelector('.nameArea');
const products = [];
const render = () => {
nameArea.innerHTML = '';
products.forEach(product => {
const newProduct = document.createElement('li');
newProduct.textContent = product;
nameArea.appendChild(newProduct)
});
}
const handleAdd = (e) => {
products.push(nameInput.value);
nameInput.value = '';
render();
};
document.querySelector('#add').addEventListener('click', handleAdd);
<input name="product" />
<button id="add">Add</button>
<ul class="nameArea"></ul>
Everytime you call the getinnerText function, a new arr variable is created.
After the function is executed, the array will not longer available.
The solution is to move the const arr = [] out from the function declaration like such:
const arr = []
function getinnerText() {
const newProduct = document.createElement("li");
newProduct.textContent = `${name}`;
document.querySelector(".nameArea").appendChild(newProduct)
arr.push(name)
}
You can declare const arr = [] outside from the function getinnerText. So you can get every names you have entered. Otherwise you create an new array every function call.
const arr = []
function getinnerText() {
const newProduct = document.createElement("li");
newProduct.textContent = `${name}`;
document.querySelector(".nameArea").appendChild(newProduct)
arr.push(name)
}
Hope solve the issue :)
You can declare your array outside function and then append your value accordingly inside the function.
const arr = [];
function myFunction(){
const newStr = document.createElement("li");
let inputVal = document.getElementById("inputText").value
newStr.innerHTML= inputVal;
document.getElementById("foo").appendChild(newStr);
arr.push(inputVal);
document.getElementById("inputText").value = "";
console.log(arr);
}
#foo {
display:flex;
flex-direction:column;
}
<input id="inputText" type="input" onchange="myFunction()">
<div id="foo"></div>
Is there any benefit to checking if an array has length before using forEach?
Consider the following:
const foo = () => {
const elements = [...document.querySelectorAll(".selector")];
elements.forEach(element => {
element.style.height = `${element._someProperty}px`;
});
};
This is what I have in my project. In some cases the elements array will be empty because the existence of such elements is based on user-input.
My question is:
Should I add something like
if (!elements.length) return;
before calling the forEach method like so
const foo = () => {
const elements = [...document.querySelectorAll(".selector")];
if (!elements.length) return;
elements.forEach(element => {
element.style.height = `${element._someProperty}px`;
});
};
Or would that be considered a micro-optimization?
I wrote a simple piece of software that allows users to "register" a function when a state is set.
This was easily achieved by adding functions to an array.
I want to return a function that is able to "unregister" that particular function.
Note that a user might register the same function twice. This means that the "unregistering" function cannot be based on the function as a key in a map
The only thing that springs to mind is making the "register" function way more complex, where each item in the "callbacks" array is not just a function, but an object like this:
{
id: someId
fn: [the function]
}
And that the unregister function will filter the someId value. But I just can't like this.
Ideas?
const state = {}
const callbacks = []
const register = (fn) => {
callbacks.push(fn)
return () => {
console.log('Unregister function. HELP!!! How do I do this?')
}
}
const setState = async (key, value) => {
state[key] = value
for (const fn of callbacks) fn(key, value)
}
const getState = (key) => {
return state[key]
}
const f1 = () => {
console.log('f1')
}
const f2 = () => {
console.log('f2')
}
const unregF1a = register(f1)
const unrefF1b = register(f1)
const unregF2 = register(f2)
setState('some', 'a')
unregF1a()
setState('some', 'b')
Loop through your callbacks and remove the desired function (works if the same function is registered twice).
You could do a simple for loop:
function unregister(fn) {
for (let i = callbacks.length - 1; i >= 0; i--) {
if (callbacks[i] === fn) {
callbacks.splice(i, 1)
}
}
}
Or you can use let and replace the whole array:
let callbacks = [];
function unregister(fn) {
callbacks = callbacks.filter(cb => cb !== fn)
}
If you want to be able to register the same function more than once and be able to unregister them independently, then yes, you'll need to track some kind of id.
An id can be something simple, like an increasing integer, and you can store them in a different array, in the same index the function is in the callbacks array (that's hashing).
Something like this:
const state = {}
const callbacks = []
const ids = []
let nextId = 0
const register = (fn) => {
const id = nextId
callbacks.push(fn)
ids.push(nextId)
nextId++
return () => {
// find the function position using the ids array:
const fnIndex = ids.findIndex(cbId => cbId === id)
if (fnIndex === -1) return // or throw something
// Now remove the element from both arrays:
callbacks.splice(fnIndex, 1)
ids.splice(fnIndex, 1)
}
}
This way, the unregister function always looks for the exact index where the id/fn resides.
I'm a beginner to React; I understand that setState is asynchronous, but I don't understand why in the example pen below the box below the refresh is not done immediately, and only updates after the second character is input.
Codepen: (updated to link to correct pen)
https://codepen.io/anon/pen/odZrjm?editors=0010
Portion:
// Returns only names matching user's input
filterList = (term) => {
let finalList = []
for (let x = 0; x < this.state.names.length; x++) {
if (this.state.names[x].toLowerCase().includes(term)) {
finalList.push(this.state.names[x])
}
}
finalList.sort()
return finalList
}
// Returns the name list as string
listNames = () => {
let filteredNames = [], filter = this.state.filter.toLowerCase(), names = ""
if (filter === "") return "Enter filter chars"
// Generate filtered array with only names matching filter
filteredNames = this.filterList(filter)
// Build and return comma-separated list of filtered names
names = filteredNames.join(', ')
if (names.length === 0) return 'None found'
return names
}
handleChange = (event) => {
let result, alert = 'alert-primary'
result = this.listNames()
this.setState({
filter: event.target.value,
namesList: result
})
}
If I replace the line "result = this.listNames()" in handleChange with just "result = 'test'" then it updates immediately. Can you please explain why it does not when I call the functions?
It occurs because you are calling the listNames method before this.state.filter is set, thus reaching:
if (filter === "") return "Enter filter chars"
Here's a working pen: https://codepen.io/anon/pen/gzmVaR?editors=0010
This is because listNames() is being called before setState
when you call handleChange method, it can't find 'this' scope.
so 'this' is undefined and can't call this.listNames().
solution:
use onChange={this.handleChange.bind(this)}
instead of onChange={this.handleChange}
This question already has answers here:
How to update nested state properties in React
(36 answers)
Closed 4 years ago.
why is this so difficult to do or find an answer for?
I have my state
state: {
people: [
{name: 'tom'},
{name: 'rich'},
]
}
why is it so hard to update the name Tom to Pete for example?
const people = this.state.people.slice();
people[i].name = value;
this.setState({ people });
I can do this but 1) i is undefined and 2) it just seems messy
is there not a more elegant solution to update object keys??
If you try to rename first element you need to pass integer index:
const people = this.state.people.slice();
people[0].name = value;
this.setState({ people });
If you need rename element specify by name tom, use for example es6 .findIndex method:
const people = this.state.people.slice();
const index = people.findIndex(item => item.name === 'tom');
if(index >= 0) {
people[index].name = value;
}
this.setState({ people });
Assuming you are printing name in JSX as below
const { people } = this.state;
return people.map((person, index) => {
<p
onClick={() => {
const newPeople = people.slice();
newPeople[index] = 'some calculation to find new name';
this.setState({ people: newPeople });
}}
>
{person.name}
</p>
});
you aren't looping though anything so yes i is undefined.
lets say you have a click handler for an item..
{this.state.people.map( (person, idx) => <div onClick={this.handleClick.bind(this, idx)>Person here!</div>)}
now from here you have an index to update the record from...
then your person to update you can get in the handle function const person = {...this.state.people[idx]}
notice i make a new object here for the person so were sure to not mutate a state object directly. assuming you have another value or state variable somewhere you can then assign that value to that person
EDIT:
handleClick = (idx, e) => {
const { value } = e.target;
const robots = [].concat(this.state.robots);
const robot = robots[idx]
robot.name = value;
robots[idx] = robot;
this.setState({robots})
}