update object key in react array state [duplicate] - javascript

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

Related

React & Javascript optimization - arrays, spread, splice

I have a little problem understanding something. So I need your help to do an example for me. Please kindly write this two functions with a better solution.
the setTempRecipe is a coming from useState hook in my react functional component.
const addCustomizationOption = (co) => {
const tmpR = Object.assign({}, tempRecipe);
tmpR.customizationOptions.push(co);
setTempRecipe(tmpR);
};
and the second one is:
const removeCustomizationOption = (co) => {
const tmpR = Object.assign({}, tempRecipe);
const g = tmpR.customizationOptions.findIndex(item => item.id === co.id);
tmpR.customizationOptions.splice(g, 1);
setTempRecipe(tmpR);
};
const addCustomizationOption = (co) => {
setTempRecipe({
...tempRecipe,
customizationOptions:[...tempRecipe.customizationOptions,co]
});
};
const removeCustomizationOption = (co) => {
setTempRecipe({
...tempRecipe,
customizationOptions:tempRecipe.customizationOptions.filter(i => i.id !== co.id)
});
};
You have the right idea, you shouldn't mutate state variables, instead take a deep copy and apply new values. What I would go for is:
const addCustomizationOption = (co) => {
setTempRecipe(tr => ({...tr, customizationOptions: [...tr.customizationOptions,co]}));
};
This spread operator will add co to the tempRecipe array and tr refers to the current state.
And, for the second one, you can filter the array:
const removeCustomizationOption = (co) => {
setTempRecipe(tr => ({...tr, customizationOptions: tr.customizationOptions.filter(recipe => recipe.id !== co.id))})
};
Since .filter() function returns a new array, you can directly filter out the id and set the new filtered array.

Insert element inside array

I have a function
checkName(output) {
output.filter((NewData) => {
return this.props.elements.filter((OldData) => {
if (NewData.key == OldData.key) {
NewData.name = OldData.name,
//there i need to add another element
// Need to add newData.number = OldData.number
}
return NewData
})
})
return output
}
and I call this function like:
const named = this.checkName(product.rows)
Now I need to add to my product's array that I passed to checkName the value "OldData.Number" to "newData.Number" that is not defined in product (so I need to create this field)
For example:
Product before the checkName function
product.rows = [NewData.name]
Product after the checkName function
product.rows = [NewData.name="value of OldData.name", NewData.number="value of OldData.number"]
How can I obtain this result?
There are 2 confusing things in your code:
You are using filter to execute an action in each member of the output array. However, filter should be used to... well, filter that array, meaning that is should not modify it, just return a sub-set of it. Instead, you might want to use forEach. However, taking into accound the next bullet, probably you want to use map.
You are modifying the array passed to the checkName function. This is confusing and can lead to hard-to-find bugs. Instead, make your function "pure", meaning that it should not mutate its inputs, instead just return the data you need from it.
I would suggest some implementation like this one:
checkName(output){
return output.map((NewData) => {
// find the old data item corresponding to the current NewData
const OldData = this.props.elements.find(x => x.key === NewData.key);
if (OldData) {
// If found, return a clone of the new data with the old data name
// This uses the spread syntax: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
return {
...NewData, // Clone the NewData object
name: OldData.name, // set the value found in OldData.name in the "name" field of the cloned object
number: OldData.number, // You can do the same for each field for which you want to replace the value cloned from NewValue
};
} else {
// Otherwise, just return a clone of the NewData
return { ...NewData };
}
}
}
The usage would be like this:
const named = this.checkName(product.rows)
Be aware that the product.rows array won't be modified!
You can get keys and values of the old object.
const keys = Object.keys(oldObject);
const values = Object.values(oldObject);
// or
const [keys, values] = Object.entries(oldObject);
After, you will create a loop with all keys of oldObject, and insert in newObject like a array.
keys.forEach( (key, index) => newObject[key] = values[index]);
// or
for (const [key, value] of Object.entries(object1)) {
newObject[key] = value
}
Use map like this.
checkName(output){
return output.map(( NewData) =>{
this.props.elements.forEach((OldData) => {
if (NewData.key == OldData.key) {
NewData.name = OldData.name;
NewData.number = OldData.number;
}
})
return NewData;
})
// return output;
}

Remove an object's key and value using a variable from function

Hey I'm trying to remove a key:value pair from state inside a Javascript Object.
It works when I hardcode the key name in the code, but when I try to use a variable from a function call, it does nothing.
Can somebody help me out?
Here's an object example:
toppingsSelected: {
"Onion":"true",
"Mushrooms":"true",
}
This works, hardcoded:
deleteTopping = toppingName => {
const { Onion, ...withoutOnion } = toppingsSelected;
console.log(withoutOnion); // Returns object without onion
};
This doesn't work:
deleteTopping = toppingName => {
const toppingName = "Onion"; // Variable gets passed in
const { toppingName, ...withoutOnion } = toppingsSelected;
console.log(withoutOnion); // Returns original object, no change made
};
So I'm basically trying to remove a key from React state but I'm pretty new to Javascript.
How can I make Javascript aware that toppingName is a key?
Another option is to add square brackets arround toppingName, and assign it to a variable. As #Bergi pointed out in the comments, this option does not mutate toppingsSelected
const toppingsSelected = {
"Onion":"true",
"Mushrooms":"true",
};
const toppingName = "Onion";
const {
[toppingName]: topping,
...withoutOnion
} = toppingsSelected;
console.log(JSON.stringify(withoutOnion));
To set the React state, you'd then do this
this.setState({ toppingsSelected: withoutOnion })
You can use delete e.g.
delete toppingsSelected[toppingName];
One way of doing this is using Array.prototype.filter()
const _obj = {
'Onion': true,
'notOnion': false
};
const newObj = Object.keys(_obj)
.filter(key => key !== 'Onion')
.reduce((acc, cur) => ({ ...acc, cur }), {})
console.log(newObj); // { notOnion: false }
This will return a new object without the 'Onion' property

ReactJS state updates on second onChange

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}

create react state name with variable reference?

i want to create state like this:
componentWillReceiveProps(nextProps) {
nextProps.columns.forEach((c) => {
const name = nextProps.columns[nextProps.columns.indexOf(c)];
this.setState({ `${name}`: (this.props.activeHeaders.indexOf(c) > -1) });
console.log(`${name}`);
});
}
I am mapping on my array columns, so each item on the array, i want to set state on them as key, is there a possibe way?
Is there a possible way?
Yes, but the way you are trying is not correct, instead of calling setState inside loop, first prepare an object with all the key-value, then pass that object to setState.
Like this:
componentWillReceiveProps(nextProps) {
let obj = {};
nextProps.columns.forEach((c, i) => {
const name = nextProps.columns[nextProps.columns.indexOf(c)];
obj[name] = this.props.activeHeaders.indexOf(c) > -1;
});
this.setState(obj);
}
Didn't get the meaning of this line:
const name = nextProps.columns[nextProps.columns.indexOf(c)];
componentWillReceiveProps(nextProps) {
nextProps.columns.forEach((c) => {
const name = nextProps.columns[nextProps.columns.indexOf(c)];
this.setState({ [name]: (this.props.activeHeaders.indexOf(c) > -1) });
console.log(`${name}`);
});
}
This should do the job

Categories

Resources