Spreading array to set a new state - javascript

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.

Related

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

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

best way to change value of an object from an array of objects when given a key

I'm working (learning) in React. I have an array of objects which hold info on gamertags. They are rated with up to five stars and this function is called when a user clicks on the stars in the GUI to alter the rating.
my solution:
I make a copy of state, iterate over the copy, check each entry for the key, reassign the number of stars, then use my setState hook to assign the altered array.
Is there a more concise way to do this? I searched all over stack and google and couldn't find anything. I feel like I should be able to map, use an arrow function and or a ternary. Thanks for any comments on styles, JS and ES6 seems to be all about that. Thx fam.
function changeStars(stars, key) {
console.log(stars, key);
const newRatingInventory = [ ...tagInventory];
for (const [index] of newRatingInventory.entries()) {
if (newRatingInventory[index].timeStamp === key) {
newRatingInventory[index].stars = stars;
}
}
setTagInventory([...newRatingInventory]);
Using the spread syntax doesn't creates a deep copy - it just creates a new array but the objects are not cloned. So any changes made to any object inside the new array will mutate the original object.
Currently you are mutating the state directly which is not the correct way to update the state in React.
You should use the .map() method to iterate over the array, create and return a new object if the condition newRatingInventory[index].timeStamp === key evaluates to true.
function changeStars(stars, key) {
const newState = tagInventory.map(obj => {
if (obj.timeStamp === key) {
// return a new object with the updated
// value of "stars" property.
return { ...obj, stars };
}
// if the above condition is not true,
// return the current object as it is.
return obj;
});
// update the state
setTagInventory(newState);
}
There is a multipal way to do this
my recommendation one is map.
const changeStars = (stars, key) => {
let tempRating = tagInventory && tagInventory.length > 0 &&
tagInventory.map(item => item.timeStamp === key ? {...item, stars} : item);
setTagInventory(tempRating)
}

JS Assigning Object To Object Causes Duplicate Objects

This seems so simple in my head but I can't seem to get the correct output. All I'm trying to do is run through a list of data and assign an object property with another object like so
{
Foo: {
firstname: "sally",
lastname: "jenkins"
},
Bar: {
firstname: "john",
lastname: "smith"
}
}
however with this code every element in the object gets assigned the same values.
let formInfo = {}
let spriteAndId = {}
forms.forEach((form) => {
spriteAndId["id"] = parseInt(form.pokemon.url.substr(form.pokemon.url.length - 6), 10);
spriteAndId["url"] = spriteUrlGen(spriteAndId["id"]);
formInfo[form.pokemon.name] = spriteAndId;
})
I apologize if that is hard to understand but all it does is assign a url ("https://example.com") and id (1234) to the empty object spriteAndId. The variable forms is an array with data already in it.
If I log spriteAndId in the loop I get the correct output with brand new values on each iteration, but it seems it just assigns only one of those iterations to every element in the parent object.
I thought maybe I didn't understand forEach well enough so I implemented it using a for loop and got the same outcome. I can't wrap my head around why it would assign the same object to all the values of formInfo.
Hopefully someone can shed some light on what I'm missing here. Thanks in advance!
const formInfo = Object.keys(form)
.map(obj => ({
[form[obj].name]: {
id: form[obj].id,
sprite: spriteUrlGen(form[obj].id)
}
}))
.reduce((prev, curr) => ({ ...prev, curr }, {}))
If I understood what you are trying to achieve is a final form object with the name of the pokemon as key and the data (sprite and Id) as value. Object.keys() returns an array of all the properties of an object. You can them map over them and transform the data, in this case we are returning an object with the pokemon name as key and id, sprite as values. Notice the [] that enclose the key of the object, that is what makes the value dynamic, otherwise if you write hardcode the key it won't work.
Now all these values have been mapped/transformed to objects, the reduce functions takes them all and encloses them in a single object, starting from {} an empty object

How to mutate original array in Javascript .map() function?

For eg:
var persons = [{ "name":"A", "salary":1200 }, { "name":"B", "salary":"1500" }];
And you want to change the value of "salary" of each person in an original array.
If you want to mutate the original array, you can use Array#forEach function.
const persons = [{ "name":"A", "salary":1200 }, { "name":"B", "salary": 1500 }];
persons.forEach(item => item.salary += 1000);
console.log(persons)
Array#map creates a new array of the created items and returns that. After you need to assign the returned result.
let persons = [{ "name":"A", "salary":1200 }, { "name":"B", "salary": 1500 }];
persons = persons.map(item => {
item.salary += 1000;
return item;
});
console.log(persons);
You can mutate the objects directly iterating with map. If I understood you correctly.
persons.map(i => { i.salary = i.salary * 1.25; return i; });
console.log(persons);
// [{ "name":"A", "salary": 1875 }, { "name":"B", "salary": 2343.75 }]
Though it's anti-pattern and you should avoid performing mutations of your original array in map().
Use forEach. If your array is full of objects, and you simply want to mutate those objects — like in the original question — then you can simply have your callback function mutate the item passed to it in its first argument.
If your array is full of primitives, which can only be changed by assigning something else to the array slots, thereby mutating the array — or if for some other reason you want to reassign things to the array slots — you can still use forEach. The second parameter and third parameters passed to the callback are the array index of the element currently being operated on, and a reference to the array itself. So within the callback function, you can assign to the relevant array slot.
you can use a simple for loop for that
var persons = [{ "name":"A", "salary":1200 }, { "name":"B", "salary":"1500" }];
for(let element of persons){
element.salary*=2;
}
console.log(persons);
.map() function takes third parameter in its callback thats the instance of original array.
You could do something like this also:
var persons = [{ "name":"A", "salary":1200 }, { "name":"B", "salary":1500 }];
persons.map(function(person, key, array) {
array[key].salary *= 2;
});
console.log(persons);
I read other answers, you can use any of them, but I see some problems there.
I will mention 2 methodologies I have used in many different languages, map and forEach. map is a functional way of traversing a collection and creating some new collection with new (different or same) elements, independent of languages. With map, it is expected to create a new collection that is created by some mapping from initial collection. On the other hand, forEach is a method that eases traversing a collection by not using usual for loop syntax for collections, and mutating (or changing) each item if desired.
If you use map on a collection that contains objects, and change those objects in the mapper function, you might face with unexpected behavior. Beacuse you are changing directly the object you are operating on, and do not mapping it to another object. This object might can be considered as a state and computers works based on the state transfers. If you want to change that object, i.e. some state, it is absolutely ok, but based on the description, you should not use map for such a case. Because you are not creating a new array with some new values, but instead, mutating provided elements. Use forEach for such a case.
I have added an example here. You can click the link and take a look at the console, and see my what I mean in a more clear way.
As far as I know, based on my experience, mutations in map method is considered as bad practice and discouraged.
These two are added for different purposes and it would be better to use them as expected.
For more, see Mozilla Web Docs page for Array.
JavaScript has an inbuilt Array method map that iterate the values of an Array
persons.map(person => person["salary"] + 1000)
var persons = [{ "name":"A", "salary":1200 }, { "name":"B", "salary":"1500" }];
var mutatedPersons = persons.map(function(obj){
return {name:obj.name,salary:parseInt(obj.salary) + 100};
})
console.log(mutatedPersons);
try:
let persons = persons.map((person) => {person['salary'] = parseInt(person['salary']) + 1; return person})
If you have an array of primitives, you can use this function:
function mapInplace<T>(arr: T[], callback: (v: T, i: number) => T) {
for(const [i, v] of arr.entries()) {
arr[i] = callback(v, i)
}
}
Example usage:
mapInplace(weights, w => w / total)
There's no return value since it's mutating the array.

Categories