React setState is incorrectly updating - javascript

const [cartItems, setcartItems] = useState([] as Product[]);
const addItemToCart = (product: Product) => {
setcartItems((preCartItems) => {
const updatedcart = [...preCartItems];
if(!updatedcart.length)
updatedcart.push(product)
// updatedcart[0].price original value 1
updatedcart[0].price = updatedcart[0].price + 1 ;
console.log(updatedcart[0].price); // prints 2
console.log(updatedcart); // printed object is saying price value 3
return updatedcart;
});
};
I am unable to understand how the value is incremented automatically by 1 and why is the value on the object giving a different output compared to the value accessed by the dot operator

The problem is that you're mutating your state within the setcartItems.
Doing const updatedcart = [...preCartItems]; is not copying the objects in preCartItems, it's just spreading the reference of the same objects in a new array.
Then you're mutating the object here updatedcart[0].price = updatedcart[0].price + 1 ;.
This will lead to two different versions of the state, hence the inconsistent result.
If you want to copy the objects you should do
const updatedcart = preCartItems.map(item => ({ ...item }));
This returns a new array of new objects and you can do any operation you want on them.
This way of copying an array of objects is also shallow, it only works for 1 depth level, if you want to copy also nested objects you need a more robust function.

Related

All nested arrays getting updated (and not just one) in React state [duplicate]

This question already has answers here:
Array.fill(Array) creates copies by references not by value [duplicate]
(3 answers)
Closed 8 months ago.
I have nested arrays of empty strings inside a React state.
function App() {
const emptyWord = Array(5).fill("");
const initialArr = Array(5).fill(emptyWord);
const [words, setWords] = useState(initialArr);
I am trying to update them like this (the indexes are just an example):
const handleInput = input => {
const currWords = words;
currWords[0][2] = input;
setCurrLetter(input);
setWords(currWords);
}
But instead of updating just the array with index 0 in words, this updates every array. I tried different approaches, also using the spread operator, and can't get this to behave properly.
Any ideas?
There are a few issues with your code. The issue you're facing is that .fill() populates your array with the same emptyWord array refrence for each index. You want to change this so that you create a new unique inner arrays for each element, which you can do with Array.from() and it's mapping function:
const [words, setWords] = useState(() => Array.from(
{length: 5}, () => Array(5).fill("")
));
You'll notice that I've put the array creation logic inside of useState() hook so that you don't recreate your array needlessly every time App rerenders.
Now that you have unique arrays in each index, you won't experience the issue of having each array updated each time you update one of them.
However, your code still has an issue where you are mutating your state directly, as const currWords = words; doesn't make a copy of your array. Modifying your state directly can cause rendering issues where React doesn't update your UI to reflect the new state. To immutably update your state you should be creating a new array with a new inner element for the item you want to update, and not updating your state directly. This can be done with .map() for example:
const handleInput = input => {
const outerIdx = 0, innerIdx = 2;
setCurrLetter(input);
setWords(currWords => currWords.map((inner, i) => i === outerIdx
? inner.map((val, j) => j === innerIdx ? input : val)
: inner
));
}
You could also make a deep copy with JSON.stringify() + JSON.parse()1, but it wouldn't be as efficient as the .map() option shown above:
const currWords = JSON.parse(JSON.stringify(words));
currWords[0][2] = input;
setCurrLetter(input);
setWords(currWords);
1: If you can support it, you can use structuredClone() instead.

Why doesn't the spread operator add properties to my array?

I'm working with a React useState variable. I have an array of objects that has 18 objects at this top level. I'm trying to update the object at the 14th index and then return the remaining objects after it. At first I was directly mutating the state by pushing my changes to it like so:
setFormWizard(wizard => {
wizard.controls[14].trash = true;
return ({
...wizard,
wizard: wizard.controls[14].group.push(...newSection.slice(0, 5)),
});
});
This works, but I'm told this isn't good practice because React may not catch the update if you push directly to the state in certain cases. So now I'm trying to use the spread operator instead.
What's happening is the first 14 objects are returning. The object I'm modifying is returning, but the rest of the objects that come after the 14th index are not returning. What am I doing wrong?
setFormWizard(wizard => {
const subjectControls = wizard.controls[14]
const groups = [...controls.group, ...subjectAddressCopy.slice(0, 5)]
return ({
...wizard,
controls: [
...wizard.controls.splice(0,14), // keep first 14 entries
{
...subjectControls,
group: groups,
trash: true
} // modify the 15th entry
...wizard.controls.splice(15) // this doesn't return the remaining controls
],
});
});
bcz splice changes in actual array you need to use slice
const arr = [1, 2, 3, 4]
arr.splice(0,2)
console.log('splice change in array so actual value of arr is :', arr)
const arr1 = [1,2,3,4,5]
// it will take slice(start, end) but end not included in return value
const cutSection = arr1.slice(1, 3);
console.log('portion of array', cutSection)
You might've wanted to use slice to return everything upwards from index 15.

Prevent original array from getting changed

I have an array that consists of some geoJSON data. I want to create a new array consisting of the data with one more key in the properties section. But it keep changing the original array. How can I prevent it from changing the orignial array?
const [citys, setCitys] = useState(someGeoJson)
const [manipulatedArray, setManipulatedArray] = useState([])
function createManiArray() {
let currentManipulatedArray = []
citys.features.forEach((city) => {
let currentCity = city
currentCity.properties = { ...city.properties,
value: Math.random()
}
currentManipulatedArray .push(currentCity)
//My problem now is that this changes the orginal city array, how can i prevent that from happening?
})
setManipulatedArray(currentManipulatedArray)
}
I think many of this kind of problems arise in the moment you use a forEach to essentially map values to another list.
The "map" method on an array does exactly that
const manipulated = citys.map(city => ({
...city.properties,
value: Math.random
}));
This way you don't have to worry about references / modifying your original array.
P.S. it is also worth noting that storing a variable with useState that's essentially derived from another state variable is not an ideal thing to do.
You might want to reconsider how your state is managed to essentially have a single source of truth (being your "citys") variable :)
You're not using setManipulatedArray to set the state.
You should be using map to create a new array of objects instead of mutating the original array.
const [cities, setCities] = useState(someGeoJson);
const [manipulatedArray, setManipulatedArray] = useState([]);
function createManiArray() {
// Create a new array of objects with an updated
// properties object
const updated = cities.features.map(city => {
return {
...city,
properties: {
...city.properties,
value: Math.random()
}
};
});
// Set the new state with that array
setManipulatedArray(updated);
}

useState creating multiple arrays

I am creating a dropdown filter to update the search results of a page using react hooks. Basically, I am passing an array with the options that the user chose from the dropdown menu. I am successfully updating the global state with the new arrays BUT my issue is useState creates a NEW array instead of merging the results with the previous state.
Above you can see, I made two calls with different filter options and the global state now holds 2 arrays. My goal is to have both arrays merged into one.
This is the function where the global state is being updated.
const Results = () => {
const [filterList, setFilterList] = useState([])
const setGlobalFilter = (newFilter) => {
let indexFilter = filterList.indexOf(newFilter);
// console.log("Top level index", indexFilter)
indexFilter ?
setFilterList([...new Set([...filterList, newFilter])]) :
setFilterList(filterList => filterList.filter((filter, i) => i !== indexFilter))
}
// console.log("TopFilterSelection:", filterList)
return (
<div>
<Filter setFilter={(filterList) => setGlobalFilter(filterList)}/>
</div>
)
}
I've been checking on using prevState like this:
...
setFilterList(prevState => [...new Set([...prevState, newFilter])]) :
...
But I don't know what I am doing wrong.
Any feedback would be much appreciated!
This happens because newFilteris an array, not a word.
Should be
setFilterList(previous => [...new Set([...previous, ...newFilter])])
Also this
let indexFilter = filterList.indexOf(newFilter);
always returns -1 if newFilteris an array (since you a sending brand new array each time), it's not a falsy value, be careful
Use the .concat method.
setFilterList(filterList.concat(newFilter))
Read more about it here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat

spread in setState of react

I saw this in somewhere and I'm confused. I've never do thing this way, what it does actually?
doSomething = index => {
const tempState = this.state
tempState.keys.push(index)
this.setState({
...tempState
})
}
const tempState = this.state
The above assigns to tempState a reference to the state object
tempState.keys.push(index)
tempState has a property of keys that holds a reference to an array and push is called on the array to add index to the array
this.setState({
...tempState
})
this.setState is called and a new copy of the state is passed into setState.
The spread operator is basically copying the content of the old state into a new object.
The above implementation isn't the perfect one and should be improved so that you aren't mutating the original state object.
doSomething = index => {
const tempState = this.state // assing reference of state to tempState
tempState.keys.push(index) // push a value to `keys` property value within state. Note that Javascript object assignment works by reference and hence this will actually mutate the state object
this.setState({
...tempState
}) // call setState to update state and pass the cloned version of tempState using spread syntax as the new state values
}
Although the above implementation clones the value before setState, its incorrect since it should clone the value before any update is made into it
doSomething = index => {
const keys = [...this.state.keys]
keys.push(index)
this.setState({
keys
})
}
However since you use ES5, in order to clone the value, you can make use of concat to update the keys array using functional setState like
doSomething = index => {
this.setState(prevState => ({
keys: prevState.keys.concat([index])
}))
}
I hope the above answer provides an insight into the explanation that you were looking for

Categories