Set an array inside an array of objects using react hooks - javascript

If we have an array that contains objects that each contain and array of tags like shown below:
const arr = [
{
0: {
name: 'Apple',
tags: ['fruit', 'green']
}
},
{
1: {
name: 'ball',
tags: ['round']
}
},
{
2: {
name: 'cat',
tags: ['grey', 'meow', 'treats']
}
}
];
Is it possible to use react hooks to update the array of tags? I was trying something like this but got confused:
setArr((prev =>
([...prev,
({...prev[id],
[...prev[id]['tags'],
prev[id]['tags']: newArrOftags ]})],
));

Here's the simplest syntax that I would use to target a specific item in your array, given you know the number value used within that item, and then update the previous state within your useState hook:
const lookup = 1; // Using the correct number value to target ball
const newItem = 'Additional ball tag here';
setArr((prevArr) => {
const newArr = prevArr.map((item) => {
if (item[lookup]) {
item[lookup].tags = [...item[lookup].tags, newItem];
}
return item;
});
return newArr;
});

Instead of using short hand syntax which is a bit complex in your case, here is what you need. I am looping through the array using map and finding the object with id. Then appending the tags to that object's tags array. In the example I am adding a few tags to an object with id 1.
let arr = [
{
0: {
name: 'Apple',
tags: ['fruit', 'green']
}
},
{
1: {
name: 'ball',
tags: ['round']
}
},
{
2: {
name: 'cat',
tags: ['grey', 'meow', 'treats']
}
}
];
const id = 1;
const tags = ["test1","test2","test3"]
arr = arr.map((a)=>{
if(Object.keys(a).includes(id.toString()))
{
a[id].tags = [...a[id].tags,...tags];
}
return a;
})
Use the above logic instead of spread operator to set state. map returns a new array so it's safe to use for state updates.
Here is an example of the logic: https://stackblitz.com/edit/js-xqnjai

Related

Why doesn't reassigning the parameter element in forEach work

For the following code block:
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach((item, i) => {
item = {
...item,
...changes
}
})
console.log(items) // items NOT reassigned with changes
items.forEach((item, i) => {
items[i] = {
...item,
...changes
}
});
console.log(items) // items reassigned with changes
Why does reassigning the values right on the element iteration not change the objects in the array?
item = {
...item,
...changes
}
but changing it by accessing it with the index does change the objects in the array?
items2[i] = {
...item,
...changes
}
And what is the best way to update objects in an array? Is items2[i] ideal?
Say no to param reassign!
This is a sort of a fundamental understanding of higher level languages like JavaScript.
Function parameters are temporary containers of a given value.
Hence any "reassigning" will not change the original value.
For example look at the example below.
let importantObject = {
hello: "world"
}
// We are just reassigning the function parameter
function tryUpdateObjectByParamReassign(parameter) {
parameter = {
...parameter,
updated: "object"
}
}
tryUpdateObjectByParamReassign(importantObject)
console.log("When tryUpdateObjectByParamReassign the object is not updated");
console.log(importantObject);
As you can see when you re-assign a parameter the original value will not be touched. There is even a nice Lint rule since this is a heavily bug prone area.
Mutation will work here, but ....
However if you "mutate" the variable this will work.
let importantObject = {
hello: "world"
}
// When we mutate the returned object since we are mutating the object the updates will be shown
function tryUpdateObjectByObjectMutation(parameter) {
parameter["updated"] = "object"
}
tryUpdateObjectByObjectMutation(importantObject)
console.log("When tryUpdateObjectByObjectMutation the object is updated");
console.log(importantObject);
So coming back to your code snippet. In a foreach loop what happens is a "function call" per each array item where the array item is passed in as a parameter. So similar to above what will work here is as mutation.
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach((item, i) => {
// Object assign just copies an object into another object
Object.assign(item, changes);
})
console.log(items)
But, it's better to avoid mutation!
It's better not mutate since this can lead to even more bugs. A better approach would be to use map and get a brand new collection of objects.
const items = [{
id: 1,
name: 'one'
},
{
id: 2,
name: 'two'
},
];
const changes = {
name: 'hello'
}
const updatedItems = items.map((item, i) => {
return {
...item,
...changes
}
})
console.log({
items
})
console.log({
updatedItems
})
As the MDN page for forEach says:
forEach() executes the callbackFn function once for each array
element; unlike map() or reduce() it always returns the value
undefined and is not chainable. The typical use case is to execute
side effects at the end of a chain.
Have a look here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
This means that although you did create new object for item, it was not returned as a value for that index of array. Unlike your second example, the first one is not changing original array, but just creates new objects and returns undefined. This is why your array is not modified.
I'd go with a classic Object.assign for this:
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach( (item) => Object.assign(item,changes) )
console.log(items)
Properties in the target object are overwritten by properties in the sources if they have the same key. Later sources' properties overwrite earlier ones.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
The other approach you can take is to use map and create a new array based on the original data and the changes:
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
const newItems = items.map((item) => {
...item,
...changes
})
console.log(newItems);
But if you need to modify the original array, it's either accessing the elements by index, or Object.assign. Attempting to assign the value directly using the = operator doesn't work because the item argument is passed to the callback by value not by reference - you're not updating the object the array is pointing at.

How to delete an array from object?

I've got an object of type : [ {name : 'xxx' , price: '555', quantity : '2' } , {...} ] and so one.
I got a class
getCartItems() {
let items = localStorage.getItem('item');
items = JSON.parse(items);
return items;
}
where i get this array.
Now i am getting index of the array, for example 0 , it should remove first array from object.
but when i do .remove, or other, it does not work. this.getCartItems()[index].remove or other does not work. Can you help me?
My guess is that you are mutating the object after you parse it and you never save it back.
You have to save the mutated object inside of your localStorage to make your removal of the first item persistant.
Look at the following example :
const localStorage = {
items: {
item: JSON.stringify([{
name: 'xxx',
price: '555',
quantity: '2',
}, {
name: 'yyy',
price: '666',
quantity: '5',
}, {
name: 'zzz',
price: '777',
quantity: '6',
}]),
},
getItem: str => localStorage.items[str],
setItem: (str, value) => {
localStorage.items[str] = value;
},
};
function getCartItems() {
const items = localStorage.getItem('item');
const parsedItems = JSON.parse(items);
// We remove the first element
const item = parsedItems.splice(0, 1);
// We save the value
localStorage.setItem('item', JSON.stringify(parsedItems));
return item;
}
console.log('First call ---');
console.log(getCartItems());
console.log('');
console.log('Second call ---');
console.log(getCartItems());
console.log('');
console.log('Third call ---');
console.log(getCartItems());
Use filter to get required items. In the following updated will not have earlier 0 index item. Now, the updated array you may want to set in localStorage again if required.
const items = getCartItems();
const indexToRemove = 0;
const updated = items.filter((,index) => index !== indexToRemove);
You can use array method filter to remove the object from array. This can look something like this:
getCartItems() {
let items = localStorage.getItem('item');
items = JSON.parse(items);
return items;
}
removeCart(){
return id; // the id that you will have from your a tag
}
const updatedItems = this.getCartItems().filter((item,index) => index !== this.removeCart()); // in updated items you will find your filtered out array of object

What is the best way to convert an array of strings into an array of objects?

Let's say I have an array of emails:
['a#gmail.com', 'b#gmail.com', 'c#gmail.com']
I need to convert it into an array of objects that looks like this:
[
{
id: 'a#gmail.com',
invite_type: 'EMAIL'
},
{
id: 'b#gmail.com',
invite_type: 'EMAIL'
},
{
id: 'c#gmail.com',
invite_type: 'EMAIL'
}
]
In order to do that, I have written the following code:
$scope.invites = [];
$.each($scope.members, function (index, value) {
let inviteMember = {
'id': value,
invite_type: 'EMAIL'
}
$scope.invites.push(inviteMember);
});
Is there any better way of doing this?
Since you're already using jQuery, you can use jQuery.map() like this:
var originalArray = ['a#gmail.com', 'b#gmail.com', 'c#gmail.com']
var newArray = jQuery.map(originalArray, function(email) {
return {
id: email,
invite_type:'EMAIL'
};
});
jQuery.map() translates all items in a given array into a new array of items. The function I am passing to jQuery.map() is called for every element of the original array and returns a new element that is written to the final array.
There is also the native Array.prototype.map() which is not supported in IE8. If you're not targeting IE8 or if you use a polyfill, then you can use the native .map():
var newArray = originalArray.map(function(email) {
return {
id: email,
invite_type:'EMAIL'
};
});
This pattern
targetArray = []
sourceArray.forEach(function(item) {
let x = do something with item
targetArray.push(x)
})
can be expressed more concisely with map:
targetArray = sourceArray.map(function(item) {
let x = do something with item
return x
})
in your case:
$scope.invites = $scope.members.map(function(value) {
return {
id: value,
invite_type: 'EMAIL'
}
});

Convert object to array of strings?

I am writing in my save method but here am getting as a list of array. but wanted to convert to set of string.
mySys: $scope.stringArrayToObjectArray($scope.editmySystems,"name");
$scope.editmySystems holds value-
0 :f
id: svg
name:"JASSI"
1 :f
id: svg2
name:"JASSYY"
length: 2
So for conversion i wrote one function-
$scope.stringArrayToObjectArray = function(stringArray, fieldName) {
var objectArr = [];
angular.forEach(stringArray, function(singleString) {
objectArr[fieldName]=singleString.name;
});
return objectArr;
};
currently objectArr is returning as-
name: "JASON2"
Expected o/p- objectArr should return-
name: ["JASSI","JASSYY"]
Please suggest
Basically you could just use:
var stringArray = [{id: 'svg', name: 'JASSI'}];
var objectArr = [];
stringArray.map(function(item) {
objectArr.push(item.name);
});
console.log(objectArr);
It's close but you're setting indexes on an array which will be an object. Instead, simply append the field name.
$scope.stringArrayToObjectArray = function(stringArray, fieldName) {
var objectArr = [];
angular.forEach(stringArray, function(singleString) {
objectArr.push(singleString[fieldName]);
});
return objectArr;
};
You could use Array#map and a callback which returns the wanted value of the key of the object.
The map() method creates a new array with the results of calling a provided function on every element in this array.
function getValues(array, key) {
return array.map(function (a) {
return a[key];
});
}
var data = [{ id: 'svg', name: 'JASSI' }, { id: 'svg2', name: 'JASSYY' }];
console.log(getValues(data, 'name'));

Lodash sort collection based on external array

I have an array with keys like so:
['asdf12','39342aa','12399','129asg',...]
and a collection which has these keys in each object like so:
[{guid: '39342aa', name: 'John'},{guid: '129asg', name: 'Mary'}, ... ]
Is there a fast way to sort the collection based on the order of keys in the first array?
var sortedCollection = _.sortBy(collection, function(item){
return firstArray.indexOf(item.guid)
});
Here is just a simple add to the accepted answer in case you want to put the unmatched elements at the end of the sortedCollection and not at the beginning:
const last = collection.length;
var sortedCollection = _.sortBy(collection, function(item) {
return firstArray.indexOf(item.guid) !== -1? firstArray.indexOf(item.guid) : last;
});
Input:
var data1 = ['129asg', '39342aa'];
var data2 = [{
guid: '39342aa',
name: 'John'
}, {
guid: '129asg',
name: 'Mary'
}];
First create an index object, with _.reduce, like this
var indexObject = _.reduce(data2, function(result, currentObject) {
result[currentObject.guid] = currentObject;
return result;
}, {});
And then map the items of the first array with the objects from the indexObject, like this
console.log(_.map(data1, function(currentGUID) {
return indexObject[currentGUID]
}));
Output
[ { guid: '129asg', name: 'Mary' },
{ guid: '39342aa', name: 'John' } ]
Note: This method will be very efficient if you want to sort so many objects, because it will reduce the linear look-up in the second array which would make the entire logic run in O(M * N) time complexity.
This is the efficient & clean way:
(Import lodash identity and sortBy):
TS:
function sortByArray<T, U>({ source, by, sourceTransformer = identity }: { source: T[]; by: U[]; sourceTransformer?: (item: T) => U }) {
const indexesByElements = new Map(by.map((item, idx) => [item, idx]));
const orderedResult = sortBy(source, (p) => indexesByElements.get(sourceTransformer(p)));
return orderedResult;
}
Or in JS:
function sortByArray({ source, by, sourceTransformer = _.identity }) {
const indexesByElements = new Map(by.map((item, idx) => [item, idx]));
const orderedResult = _.sortBy(source, (p) => indexesByElements.get(sourceTransformer(p)));
return orderedResult;
}
You can use indexBy(), and at() to sort your collection. The advantage being that concise code and performance. Using sortBy() here does the trick, but your external array is already sorted:
var ids = [ 'cbdbac14', 'cf3526e2', '189af064' ];
var collection = [
{ guid: '189af064', name: 'John' },
{ guid: 'cf3526e2', name: 'Julie' },
{ guid: 'cbdbac14', name: 'James' }
];
_(collection)
.indexBy('guid')
.at(ids)
.pluck('name')
.value();
// → [ 'James', 'Julie', 'John' ]
Using at(), you can iterate over the sorted external collection, building a new collection from the source collection. The source collection has been transformed into an object using indexBy(). You do this so at() has key-based access for each of it's ids.

Categories