Prevent original array from getting changed - javascript

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

Related

Spreading array to set a new state

I am working in React.JS and trying to set a new state by spreading array of objects and changing a value of one of them. I have an array of objects with key and another object as a value. Like this:
[
{"first": {
"backlog":[...]
}
},
{"second": {
"backlog":[...]
}
},
{"third": {
"backlog":[...]
}
}
]
I also have variable selected with one of object's key as a value. So I want to spread my entire array as a new state, but with a slight change to a backlog of exact object. The key of it's object is saved in selected variable. The problem is, I can't reach it the object I need. I tried to do it like this, but I understand where I was wrong:
setProjects((prevProjects) =>
([...prevProjects, [selected]: {...prevProjects[selected], backlog: [...prevProjects[selected].backlog, NewObj ]}])
)
Is it even possible to achieve? If yes, how?
You will have to find the index of the object with the key in the outer array, and then update that index while mapping over the state.
const index = projects.findIndex(obj => obj[selected]);
setProjects(
projects.map((project, i) => (
i === index
? { backlog: [...project.backlog, NewObj] }
: project
))
);
But consider if you could use an easier data structure to make manipulations of it easier.
const [projects, setProjects] = useState({
first: [...], // first backlog
second: [...], // second backlog
// etc
});
Then you can update with
setProjects({
...projects,
[selected]: [...projects[selected], NewObj]
});
You don't need to use the callback form (setProjects((prevProjects) =>) unless you've previously updated the projects state synchronously and the component hasn't re-rendered yet. Sometimes it's necessary, but usually it isn't.

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.

Unable manage objects in array independently

I'm working on a text editor using React and I want to keep track of the changes in an array. Whenever I make changes an object is added to the array (as it should) but all the other objects change as well and become the same as the new one. I'm aware of how Javascript doesn't store objects in independent variables if not reassigned so I used the spread operator to create a new array and then add a new object using Object.assign() but it's still not working and I can't figure out what I'm doing wrong.
getNewChangesHistory(update, oldChangesHistory){
var newChangesHistory = [...oldChangesHistory, Object.assign({}, update)];
if(newChangesHistory.length > 25){
delete(newChangesHistory[26]);
}
return newChangesHistory;
}
updateDocumentContent(content){
var newDocument = {...this.state.document};
newDocument.content = content;
this.setState(prevState => {return {
document: newDocument,
changesHistory: this.getNewChangesHistory(content, prevState.changesHistory),
hasChanges: true
}})
}
updateTextbox(editedProperties, key){
const newDocumentContent = {...this.state.document.content};
newDocumentContent.textboxes[key] = { //Textboxes are stored as objects of an array
...editedProperties,
id: key
}
this.updateDocumentContent(newDocumentContent)
}
render(){
return(
<TextBox
onEdit={(editedProperties) => {this.updateTextbox(editedProperties, 0)}}
/>
)
}
The problem is in updateTextbox. With {...this.state.document.content} it only creates a shallow copy. In this copy the textboxes property will still reference the same object. And you mutate that object by the assignment to its [key] property. So that mutation will be seen in all objects that have that same textboxes object reference.
One way to get rid of this, is to treat textboxes as immutable, and do this:
updateTextbox(editedProperties, key){
const {content} = this.state.document;
const newDocumentContent = {
...content,
// create a new textboxes array
textboxes: Object.assign([], content.textboxes, {
[key]: {
...editedProperties,
id: key
}
})
};
this.updateDocumentContent(newDocumentContent);
}

React setState is incorrectly updating

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.

Can my code be made more efficient if I need to update pin based on a condition in javascript?

I have a variable AllPackages which is an array of objects. I want to get all the same Products in all objects in AllPackages and update their pin with the selectedPIN if dp.product.code matches with the object's dealProducts product's code.
setProductPinOnAllPackages(dp): void {
let AllPackages = [{"dealProducts":[{"pin":"","product":{"id":"100","code":"AAA","name":"AAA"}},{"pin":"","product":{"id":"200","code":"BBB","name":"BBB"}}]},{"dealProducts":[{"pin":"","product":{"id":"300","code":"CCC","name":"CCC"}},{"pin":"","product":{"id":"200","code":"BBB","name":"BBB"}},{"pin":"","product":{"id":"400","code":"DDD","name":"DDD"}},{"pin":"","product":{"id":"100","code":"AAA","name":"AAA"}}]}];;
let selectedPIN = dp.pin;
//Can this be made more efficient ????
AllPackages.filter(pkg => pkg.dealProducts
.filter(pkgDealProduct => pkgDealProduct.product.code === dp.product.code)
.map(data => data.pin = selectedPIN));
}
You can make it more efficient by not constructing an unnecessary intermediate array - your goal is to perform side-effects (mutate the data), not to construct a filtered array from the AllPackages, so use a generic iteration method like forEach or for..of instead of .filter in the outer loop.
Similarly, don't use .map, since you aren't looking to map to a new array - again, you're looking for side-effects, so use a generic iteration method.
You can also extract the code from the parameter just once, instead of on every iteration.
const { code, pin } = dp.product;
for (const pkg of AllPackages) {
pkg.dealProducts
.filter(pkgDealProduct => pkgDealProduct.product.code === code)
.forEach((data) => {
data.pin = pin;
});
}

Categories