If you have an array of objects, and you want to update few properties of one of the objects inside the array. Then you could do something like this.
const index = state.findIndex(x => x.name === field);
if(index > -1) {
const newState = [...state];
newState[index] = {
...newState[index],
isValid: isValid,
content: content,
isUnchanged: false
}
setRequiredFields(newState);
}
- Find the index of the object that you want to update.
- Add properties inside that object.
- Update the react state.
Spread operator to update array of objects - javascript
Need help with useState hook - can't update an array using the spread operator
reactjs - Why use the JavaScript spread operator to update an item in an array? - Stack Overflow
reactjs - Updating state array using Spread operator in React? - Stack Overflow
I am fairly new to React and I think I am missing an important concept about state.
I am trying to update a state variable, specifically an array. The following function successfully updates the array if I use Array.push(), but not if I use destructuring/the spread operator. What am I missing? Also, the template is completely non-reactive - even when the console.log statement displays the correct value, the HTML does not.
function MyComponent() {
const [selection, setSelection] = useState([]);
const handleSelection = (id) => {
// doesn't work
// let newSelection = [...selection, id];
let newSelection = selection;
newSelection.push(id);
// Prints the correct value when using Array.push() but not destructuring
console.log(selection);
setSelection(newSelection);
};
return (
{/* never changes */}
<pre>{JSON.stringify(selection, null, 2)}</pre>
)
}
EDIT: I thought this was a problem with how I was setting state on the selection variable, but it turns out the issue was with other parts of my code that are tangentially related to this. The problem was that I was trying to to loop over some data and generate a component for each datum, but I wasn't doing that correctly. I was setting a different state variable to hold these components, and the various pieces of state were out of sync. Here is a repro of the working code, where I am mapping the components correctly (in the template directly rather than setting state, which seems to be better practice). As you can see, spread/destructuring works perfectly as expected now.
{selection}
handleSelection("testId")}>CLICK ME ); };The main reason I can see for this is to create a shallow copy of the object, so that if you perform any operations on the copy, it won't mutate the original. This would be especially useful in a functional programming paradigm where you don't want to mutate any shared objects/maintain a shared state.
However, as I mentioned, this only creates a shallow copy (as opposed to a deep copy), so that if any of the properties of the object are themselves mutable (such as an array property), then those properties would still be able to be mutated.
Edit: From the question in the comments, and building off of what @AdiH mentioned in their answer, it doesn't seem to make any sense to update post.title before creating the shallow copy as this would mutate the original object. It's possible that the author intended for this change to be reflected in both the original and the copy, but for any further changes to be reflected only in the copy
const x = { a: 'b', c: 'd', d: [] };
const copy = { ...x };
copy.a = 'c';
console.log(x); // unchanged
const same = x;
same.a = 'c';
console.log(x); // changed
copy.d.push('e');
console.log(x); // x.d has been changed
I can see your confusion. I think the example is not great. One can only guess what the author had in mind but if he or she intended to enforce immutability I would not have done post.title = "Updated" since it obviously mutates post.
Instead I would have done
post[index] = { ...post, title: "Updated"} see examples of JS Spread syntax.
So yes, I'm also unable to guess the motivation behind that example.
You can use a mix of .map and the ... spread operator
You can set the value after you've created your new array
let array = [{id:1,name:'One'}, {id:2, name:'Two'}, {id:3, name: 'Three'}];
let array2 = array.map(a => {return {...a}})
array2.find(a => a.id == 2).name = "Not Two";
console.log(array);
console.log(array2);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Or you can do it in the .map
let array = [{id:1,name:'One'}, {id:2, name:'Two'}, {id:3, name: 'Three'}];
let array2 = array.map(a => {
var returnValue = {...a};
if (a.id == 2) {
returnValue.name = "Not Two";
}
return returnValue
})
console.log(array);
console.log(array2);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Using Spred Operator, you can update particular array value using following method
let array = [
{ id: 1, name: "One" },
{ id: 2, name: "Two" },
{ id: 3, name: "Three" },
];
const label = "name";
const newValue = "Two Updated";
// Errow comes if index was string, so make sure it was integer
const index = 1; // second element,
const updatedArray = [
...array.slice(0, index),
{
// here update data value
...array[index],
[label]: newValue,
},
...array.slice(index + 1),
];
console.log(updatedArray);
To make case 2 work, you need to make a variable out of the prevState first. Then you can push to it and return that new variable.
const case2 = () => {
const newItem = {id:3,text:'Learn .Net'};
setTodos((prevState) => {
const newState = [...prevState]
newState.push(newItem);
return newState;
});
}
For case 3, you made a little mistake by making result an object instead of an array. Change the {} to [] and it should work fine, like this.
const case3 = () => {
const newItem = {id:3,text:'Learn C#'};
setTodos((prevState) => {
let result = [...prevState, newItem ] ;
return result;
});
}
In all the cases, a new array need be to created and returned.
Case 1.
return prevState.concat(newItem); [ concat create a new array ]
Case 2
const newState = [...prevState]; [ Spread Operator creates a new array ]
newState.push(newItem);
return newState;
Case 3
let result = [...prevState, newItem ] ; [ Spread Operator creates a new array and newItem is added simultaneously ]
return result;
state is an object so updating it the way you are at the moment won't work.
You can simply update only the property you need as:
this.setState({
metawords: evt.target.value,
})
Since you've mentioned spread operator, you can also (but not always necessary) update your state as:
this.setState({
...this.state,
metawords: evt.target.value,
})
or
this.setState(prevState => ({
...prevState,
metawords: evt.target.value,
}))
Should you need more info about it, I recommend you to have a look at ReactJS documentation.
You can use spread operator like this to setState.
this.setState(
{...this.state,
metawords: evt.target.value
})
However since you only want to change a single property, this will also work in your case:
this.setState({metawords: evt.target.value})
Many ways to do it, really. Here's one:
this.setState(prevState => (
{appointmentsData: {...prevState.appointmentsData}, newAppt}
));
Though your approach should work too, if you just add the appointmentsData: first. Like so:
this.setState({appointmentsData: {...this.state.appointmentsData, ...newAppt}});
This will spread all the previous values into a new object, plus the one you want to add.
I personally always use the functional way of setting the state when the new one depends on a previous state value, but your way is fine here too.
You should not use spread operator on newAppt. Just go with:
this.setState({ ...this.state.appointmentsData, newAppt});. It seems to me that the spread operator on newAppt is not getting any data for the const.