Good day,
I have a following problem.
I have two arrays. One of them is storing items that comes from API and it goes like this:
0: {id: '80682ddb-0785-ec11-94f7-281878bb3ca6', name: 'test', typeId: 14, typeName: 'typename', typeAbbreviation: 'asa'}
1: {id: '80682ddb-0785-ec11-94f7-212113ca6', name: 'number 2', typeId: 14, typeName: 'typename', typeAbbreviation: 'asa'}
2: {id: '80682ddb-0785-ec11-94f7-24124878bb3ca6', name: 'number 3', typeId: 14, typeName: 'typename', typeAbbreviation: 'asa'}
Values from this array are used to list checkboxes.
Second array is updated every time user clicks one of the checkboxes. This checkbox is being puhsed to the table with additional checked property:
0: {checked: true, id: '80682ddb-0785-ec11-94f7-281878bb3ca6', name: 'test', typeId: 14, typeName: 'typename', typeAbbreviation: 'asa'}
My goal is to push the values to the table - fill it with all checkboxes and then replace the certain checkbox with the selected one.
In general values from Api are replaced with certain selected checkboxes values with addional checked prop.
I tried to achieve it in a few ways using mapping, filtering and so on like that:
var newArray = [];
selectedItems?.forEach(function(mainObject) {
for (let i=0; i<currentItems.length; i++){
if(currentItems[i].id === mainObject.id){
newArray.push(mainObject);
} else {
newArray.push(currentItems[i])
}
}
});
I would appreciate Your help. Any ideas how to achieve it?
What I understood is that you have a list that comes from an API, and you want to display it as checkboxes, and only get the ones that the user selects.
you can use map.
I think your approach is right, you should create new array, instead of trying to delete items from the original array.
const selectedItems = [];
const onChange = (e)=>{
let id = e.target.value;
if(e.target.checked){// if check box is selected
arrayFromApi.map(item => {
if(item.id === id){
selectedItems.push(item);
}
});
}else{// if checkbox is deselected
selectedItems.filter(item => item.id === id);
}
}
Related
Sorry for the basic question and bad lexicon, I am (very) new to javascript. I have an array of data and I would like to create a subset of that data, based on selected columns. The first few rows of my data, for example:
0: {ID: 3607, Name: 'Alamo', Funds: 52933955,
Revenues: 9160109, BAT: 5, …}
1: {ID: 3539, Name: 'Alvin', Funds: 6128147,
Revenues: 964083, BAT: 0, …}
2: {ID: 3540, Name: 'Amarillo', Funds: 12450969,
Revenues: 1716038, BAT: 0, …}
I want to create a new array from columns 0, 1, 2, and 4 (ID, Name, Funds, and BAT). In the code below, toolData is the array created from the original dataset (toolData.json), and tableData is the array I'm trying to create from the selected data. selections contains the column numbers I want to pull into the new array.
var getData = () => axios.get('toolData.json')
.then(res => res.data)
.then(data => {
var toolData = data;
console.log(toolData);
var tableData = [];
var selections = [0,1,2,4];
for (i=0; i < toolData.length; i++)
{
tableData[i] = toolData[i];
for (j=0; selections.length; j++)
{
k = selections[j],
tableData[i][j] = toolData[i][k]
}
}
console.log(tableData);
This particular code snippet doesn't work at all, I'm assuming I've created an infinite loop somehow. If I comment out tableData[i] = toolData[i]; then that problem resolves, but the code still doesn't work. console.log(toolData); gives me what I'm looking for (the full panel of data), but console.log(tableData); gives the error:
javascript.js:42 Uncaught (in promise) TypeError: Cannot set properties of undefined (setting '0')
at javascript.js:42
Ultimately I would like the user to be able to choose the columns they want to include in the new array, but before I can figure that puzzle out I need to solve this one.
Well, it seems from what you're saying is that every index in the array is an object.. arr[0][0]==undefined but arr[0]['ID']==3607
function newSubset(arr,dataToSelect){
//arr is the fetched array, dataToSelect is an array of the keys(like ID,Name...) that you want from the array
return arr.map(obj=>{
var toReturn={} //object that would give each desired key for each part in arr
dataToSelect.forEach(key=>toReturn[key]=obj[key]) //placing wanted keys in toReturn
return toReturn
})
}
//usage
var getData = () => axios.get('toolData.json')
.then(res => res.data)
.then(data => {
var wantedKeys=["ID","Name","Funds","BAT"]
console.log(newSubset(data,wantedKeys))
//rest of your code here
LIVE EXAMPLE
var dataArray=[{ID: 3607, Name: 'Alamo', Funds: 52933955, Revenues: 9160109, BAT: 5}, {ID: 3539, Name: 'Alvin', Funds: 6128147, Revenues: 964083, BAT: 0}, {ID: 3540, Name: 'Amarillo', Funds: 12450969, Revenues: 1716038, BAT: 0}]
function newSubset(arr,dataToSelect){
//arr is the fetched array, dataToSelect is an array of the keys(like ID,Name...) that you want from the array
return arr.map(obj=>{
var toReturn={} //object that would give each desired key for each part in arr
dataToSelect.forEach(key=>toReturn[key]=obj[key]) //placing wanted keys in toReturn
return toReturn
})
}
console.log(newSubset(dataArray,["ID","Name","Funds","BAT"]))
The data is a JSON object. It is not indexed by numbers but rather by names.
It's also recommend to use the built-in map function for this.
const tableData = toolData.map(row => ({
ID: row.ID,
Name: row.Name,
Funds: row.Funds,
BAT: row.BAT
}));
If you want the new toolData array to contain arrays instead of objects, you can instead do:
const tableData = toolData.map(row => [
row.ID,
row.Name,
row.Funds,
row.BAT
]);
I am trying to figure out the best way to map an objects id property that is within a multi-dimension array to object values that are within another array that share the same id.
As an example i have an array of genre_ids like so:
0: {id: 1, name: 'sci-fi'},
1: {id: 2, name 'comedy'},
2: {id: 3, name: 'action'}
And an array of tv_show_genre_ids which looks like:
0: {name: ..., genre_ids: [1, 4, 9]},
1: {name: ..., genre_ids: [2, 3, 4]},
I was trying to figure out the best way to retrieve a list of the genres name by its id.
I have managed to create a working solution so far but it feels incredibly dirty as i am performing multiple nested loops and i wasn't sure if there is a cleaner more declarative approach to my solution
Here is my approach which assumes i already have a list of genre ids and names (accessed within this.genres.
this.http.get('https://api.com/shows')
.subscribe((res: array <any> ) => {
this.shows = res.results;
this.shows.forEach(show => {
show.genre_names = '';
show.genre_ids.forEach(id => {
for (const [i, v] of this.genres.entries()) {
if (id == v.id) {
if (this.genres[i] && this.genres[i].name) {
if (show.genre_names === '') {
show.genre_names = this.genres[i].name
} else {
show.genre_names += `, ${this.genres[i].name}`;
}
}
}
}
})
});
});
Is there a better way of doing this as i seem to come across this type of problem quite often when trying to map ids from one object to another within multi-dimension arrays.
Any guidance would be greatly appreciated.
EDIT:
Here is an example of the Genre Data from the API af:
0: {id: 10759, name: "Action & Adventure"}
1: {id: 16, name: "Animation"}
And here is an example of the show data from the API:
0:
backdrop_path: "/ok1YiumqOCYzUmuTktnupOQOvV5.jpg"
first_air_date: "2004-05-10"
genre_ids: (2) [16, 35]
id: 100
name: "I Am Not an Animal"
origin_country: ["GB"]
original_language: "en"
original_name: "I Am Not an Animal"
overview: "I Am Not An Animal is an animated comedy series about the only six talking animals in the world, whose cosseted existence in a vivisection unit is turned upside down when they are liberated by animal rights activists."
popularity: 10.709
poster_path: "/nMhv6jG5dtLdW7rgguYWvpbk0YN.jpg"
vote_average: 9.5
vote_count: 341
I want to add a new property to the show object named genre_names which gets the genre name via the genre response.
Your best bet is to first convert your genres into a Map or an object to use as a lookup:
const genreLookup = new Map();
this.genres.forEach(genre => genreLookup.set(genre.id, genre));
Now when you process an array of shows, you don't have to loop through the genres multiple times:
this.shows.forEach(show => {
show.genre_names = show.genre_ids
.filter(id => genreLookup.has(id))
.map(id => genreLookup.get(id).name)
.join(', ');
});
Following is my react code, which is displaying menu items and unfortunately I am not getting any id to add as a key in list. As mentioned in the docs under Keys section, it is advised to avoid Math.random(). Is it performant and better than Math.random() to use index as key while displaying the menu list ?
Code -
[{name: 'Home'}, {name: 'About'}, {name: 'News'}, {name: 'Contact'}].map((item, index) => {
return `<li key=${index}>${item.name}</li>`
})
To put it simply I wouldn't use either. The point of the key is to allow React identify the children <li>s you are creating, during render.
Using the a random key or array index will not provide a consistent key.
I'd instead add an ID to each of your objects...
[
{ id: 1, name: 'Home' },
{ id: 2, name: 'About' },
{ id: 3, name: 'News' },
{ id: 4, name: 'Contact' },
].map(({ id, name }) => {
return <li key={id}>{name}</li>
})
That way if your were to reorder the array, the ID consistently identifies the object.
I'm using lodash mapKeys to take my array of objects and convert it to a mapped object using the id property. That's simple enough, but the problem is that it's sorting the new object by id.
For example if I had three objects in my array:
let myArray = [
{
id: 3,
name: 'Number Three'
},
{
id: 1,
name: 'Number One'
},
{
id: 2,
name: 'Number Two'
}
];
Then I map the keys by id:
_.mapKeys(myArray, 'id')
It returns the following:
{
1: {
id: 1,
name: 'Number One'
},
2: {
id: 2,
name: 'Number Two'
},
3: {
id: 3,
name: 'Number Three'
}
}
My server returns the array in a specific order, so I would like the objects to remain the same, so that when I loop over the object properties, they are in the correct order.
Is that possible with this method? If not, is there a possible alternative to achieve the results?
Use a Map because each item has a custom key (like objects), but the order of insertion will be the order of iteration (like arrays):
const myArray = [
{
id: 3,
name: 'Number Three'
},
{
id: 1,
name: 'Number One'
},
{
id: 2,
name: 'Number Two'
}
];
const map = myArray.reduce((map, item) => map.set(item.id, item), new Map());
map.forEach((item) => console.log(item));
As pointed out in the comments, looping over an object doesn't guarantee order. If you want an ordered list, you need an array.
However, you could apply the iterator pattern. In this pattern, it's up to you to decide what “next” element is. So, you could have a set with the objects (in order to get them in constant time) and an array to store the order. To iterate, you'd use the iterator.
This code could be used as example.
Hope it helps.
let myArray = [{
id: 3,
name: 'Number Three'
}, {
id: 1,
name: 'Number One'
}, {
id: 2,
name: 'Number Two'
}];
let myIterator = ((arr) => {
let mySet = _.mapKeys(arr, 'id'),
index = 0,
myOrder = _.map(arr, _.property('id'));
return {
getObjById: (id) => mySet[id],
next: () => mySet[myOrder[index++]],
hasNext: () => index < myOrder.length
};
})(myArray);
// Access elements by id in constant time.
console.log(myIterator.getObjById(1));
// Preserve the order that you got from your server.
while (myIterator.hasNext()) {
console.log(myIterator.next());
}
<script src="https://cdn.jsdelivr.net/lodash/4.16.6/lodash.min.js"></script>
Like mentioned in the comments, the best would be to keep the object references both in an array to keep the order and in a hash to ease updating.
Backbone's collection (source) works like this. It keeps objects in an array (models), but automatically updates a hash (_byId) when adding and removing models (objects) or when a model's id changes.
Here's a simple implementation of the concept. You could make your own implementation or check for a collection lib.
// a little setup
var array = [];
var hash = {};
var addObject = function addObject(obj) {
hash[obj.id] = obj;
array.push(obj);
}
// Create/insert the objects once
addObject({ id: 3, name: 'Number Three' });
addObject({ id: 1, name: 'Number One' });
addObject({ id: 2, name: 'Number Two' });
// Easy access by id
console.log("by id with hash", hash['1']);
// updating is persistent with the object in the array
hash['1'].name += " test";
// keeps the original ordering
for (var i = 0; i < 3; ++i) {
console.log("iterating", i, array[i]);
}
I have a big json object I am using to control a cascading select list that I am trying to filter down to prepare for sending to the server.
The structure looks something like this -
Accounts[{name:account1, id:1, selected:false, schools:[{name:school1, id:2,selected:true}]}]
(With multiple accounts with multiple schools in each, keeping it simple for example purposes)
What I am trying to do is put it through some maps/filters and achieve an array of ids of schools that have the key of selected = true. So my attempt is to filter down by first all schools, then by schools that have selected true, then just the id's of those schools.
So here is my attempt -
$scope.schooIDsForSave = $scope.accountTreeHere.filter( function(obj){
return obj.schools;
}).filter( function(obj){
return obj.selected;
}).map(function(obj){
return obj.id;
});
This is only returning 1 ID so I'm getting something wrong here. I think I have something wrong with my usage of map/filter as I am still vey new to it. Any insight to point me in the right direction would be much appreciated! Thanks for reading.
Given structure
var schools = [{
name: 'account1',
id: 1,
selected: false,
schools: [{
name: 'school1',
id: 2,
selected: true
}]
}, {
name: 'account2',
id: 2,
selected: false,
schools: [{
name: 'school2',
id: 3,
selected: false
}]
}];
Try
var ids = schools.map(function(v) {
return v.schools;
}).reduce(function(a, b) {
return a.concat(b);
}).filter(function(v) {
return v.selected;
}).map(function(v) {
return v.id;
});