Is it possible for map to mutate object? - javascript

I have a very strange case which indicates that either map function mutates object or lodash incorrectly clones the object. This piece of code is just a sample: a function that takes an object as an argument and returns a copy of it using cloneDeep from lodash. Then I use this object to generate a chart.
const copyObject = data => {
const copy = _.cloneDeep(data);
const dataWithIndexes = copy.nodes.map((node, index) => {
node.index = index;
return node;
});
return copy;
};
export const copiedData = copyObject(sampleData);
Entry data in this case is an object with arrays of objects:
{
nodes: [
{ name: "transactions.main", layer: 0 },
...
],
links: [
{ source: 3, target: 3, value: 4 },
...
]
}
As you can see map inside function is not used at all and this is the point. When I am using original, unmodified object within my chart generate function it works fine, when I am copying the object with function shown above, it doesn't work, BUT when I comment this dataWithIndexes variable it starts working again. Is it possible in any way that map mutates the copied object? Or maybe it's lodash's fault? It may clone the object incorrectly, but on the other hand I only use the map on it, it doesn't modify it in any way.
Maybe anyone can help me solving this riddle T_T
Thanks

You are modifying the node object parameter in the map(...) callback by overwriting its index property: node.index = index. This way the original object in the array is getting mutated although it returns a new array.
This would still happen even though you are not using the dataWithIndexes that is because the map(...) is still run and when it does it mutates the objects in the copy.node array with the new values of the index in the callback.
To avoid it, make a copy of the node object parameter in the map call and assign the new index there and then return it from the callback:
const dataWithIndexes = copy.nodes.map((node, index) => {
return {...node, index};
});

Related

Trying to push a string into an array returned from another function

Hey I'm pretty new to coding. I've tried looking around for how to do this but am at a loss.
Below are the two functions, which are in an object.
listPeople: function () {
let array = []
return array;
// returns an array of all people for whom tasks exist
},
add: function ( name, task ) {
return this.listPeople().push( name )
},
If I run this, the add function returns 1. and will return 2 if I push two things in, and so on. What's going on here, how do i push the name variable from add into the array in listpeople?? The name variable is 'zeke' by the way.
Sorry in advance if this doesn't make sense :D
It should probably look more like this. Create an arr property, and push objects into it from add. You can then return that list from listPeople.
const obj = {
arr: [],
listPeople: function() {
return this.arr;
},
add: function(name, task) {
return this.arr.push({ [name]: task });
}
}
obj.add('bob', 'Shopping');
obj.add('Betty', 'Singing');
console.log(obj.listPeople());
What's happening in your code is that the add function is returning the result of the .push() method (the new length of the array).
If your intent is to actually return the array, you should modify the add method as follows:
add: function ( name, task ) {
var people = this.listPeople();
people.push(name);
return people;
},
Additionally, while changing listPeople to an array property instead of a method may work better on a shallow level, I don't know what you may have planned to implement into the listPeople method. And it may only confuse/complicate things as far as answering the question you have asked.

How do the map function that return an object with modified keys? (JS, React.js)

I am trying to create an object with modified keys after map() function (the data is from API), here is my code:
const getData = mySchedule.schedules.map((data) => ({
[data.id]: false,
}));
setCheckId(getData);
This code return:
And I want this output:
Do you have any solution for this case? thank you.
Solution:
Create an object => const getData = {};
Iterate => mySchedule.schedules
Set id as a key and false as value => getData[item.id] = false;
const getData = {};
mySchedule.schedules.forEach(item => {
getData[item.id] = false;
});
setCheckId(getData);
The above answer is correct, but let's dive deep into the problem first:
The map function returns an Array, what you want is an Object. The map function applies the callback for every element and returns a new array of elements modified by the callback function. What are you seeing is the array returned by the map function, note that 1,2,3,4is the index position of the array. Read more about the function map here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
The output that you want is an object, but as we have learned before the map function always returns an array with new elements resulting from your map function callback.
If you want an object that are many ways to achieve that, you can loop through the array and add properties to the object.
If you want to understand more and dive into array methods, you can even achieve that using reduce method, therefore the first answer is simpler:
/* Another example using reducer */
const data = [ {10:false,20:false}];
const reducer = (accumulator, currentValue) => currentValue[accumulator] = false;
setCheckId(data.reduce(reducer));

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

can I use forEach to make every element of an array a new object?

I created an array with many elements with a loop:
myArray = [c1, c2, c3...]
now I want to make each element into an object and assign different key values:
c1 = {image = path, value = number)
I tried to run forEach() but can't figure out the correct way to do so and I have not succeeded in finding the answer to it.
My guess was:
myArray.forEach(function() {
let name = {
image = path,
value = number,
}
return name;
});
but there's no change in the elements in the log.
Any help or link to an answer that can help me here. First time coding here.
UPDATE: an easier solution was to .push all the keys and values of the objects when I created the array with the loop in the first place.
array.push({image: pathx, value: numberx})
You can, but you'd be better off with a simple for loop:
for (let i = 0; i < myArray.length; ++i) {
let entry = myArray[i];
myArray[i] = {image: entry.path, value: entry.number};
}
Or making a new array with map.
newArray = myArray.map(entry => ({image: entry.path, value: entry.number}));
Or if you prefer non-arrow functions:
newArray = myArray.map(function(entry) {
return {image: entry.path, value: entry.number};
});
You could theoretically push to a new array but this is the exact usecase for Array#map. Array#map maps old values to new values. The returned object from the callback is the new object and the returned array is the new array containing the new objects.
Semantically, Array#forEach is to plainly iterate over each element in an array, and possibly execute something with side-effects (which may include pushing to another array). But with Array#map, it's specifically used to transform old array values to new ones. Use the one that is specifically designed because it conveys a clear message to anyone else who reads your code.
const newArray = myArray.map(({ path, number }) => ({
image: path,
value: number
}));
Array#map maps old values to new values. You may need to use the follwing instead of arrow functions as it is not supported in IE.
I just added dummy data in the object, you can change it.
myArray = ["c1", "c2", "c3"]
myArray = myArray.map(function(elem) {
return {"image":"path"+elem,"value":"value"+elem};
});
console.log(myArray);

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