undefiend value in array of objects Vuejs - javascript

im trying to make an array with objects but while looping i get as a result the first 83 objects as undefiend and only the last 4 with correct data. I tried to refactor the code several times but i dont seem to find a solution.
This is the console log result i get
This is the network response i get from the API
<script>
export default {
computed: {
allSales(){
var i, sales=[], x, y
for (i = 0; i <= this.salesLists.length; i++) {
sales[i] = {
x:this.date(i+1),
y:this.amount(i+1),
status:this.status(i+1),
}
}
console.log(sales);// first 83 objects undefined
return sales
},
salesLists() {
this.$store.state.sale.sales
},
},
methods:{
date(id) {
return this.salesLists.filter(sale => sale.id === id).map(sale => new Date(sale.updated_at).toISOString().slice(0,10))[0];
},
amount(id) {
return this.salesLists.filter(sale => sale.id === id).map(sale => sale.amount)[0];
},
status(id) {
return this.salesLists.filter(sale => sale.id === id).map(sale => sale.status)[0];
}
}
}

After looking at your second screenshot, I see that your salesLists has elements with ids greater than 87, or the length of the salesLists array. This is an issue, because in your for loop you are assuming that every element of the array has an id that is >= 1 and <= salesLists.length.
Because this is not the case, there are several iterations of the loop where your date, amount, and status methods return undefined.
I would recommend that you transform the salesLists array directly in the computed method in a single call to map. It might look something like this:
allSales(){
return salesLists.map(sale => {
return {
x: new Date(sale.updated_at).toISOString().slice(0,10),
y: sale.amount,
status: sale.status
}
})
},

Related

Javascript - Add Item in the middle of an array inside map function

I'm trying to add an item in a specific index inside an array inside a map function and it's been behaving unexpectedly. Here's the code for it
const addItemToLevelTwoArray= (uniqueID, arrayID )=> {
const reportObject = {
id:arrayID,
title:'',
}
data.map(section=>{
section.content.map((report, reportIndex)=>{
if(report.id===uniqueID){
section.content.splice(reportIndex, 0, reportObject);
}
return report;
})
return section;
})
}
Here's a working pen - https://codepen.io/raufabr/pen/vYZYgOV?editors=0011
Expected behaviour is that it would insert an object in the specific index, right above the object where the ID matches.
However, it's acting weirdly and sometimes I'm getting 2 items being added instead of one.
Any tip on what I'm doing would be massively appreciated! I know I'm close but I've been stuck on this for a while now and can't figure out what I'm doing wrong!
Preface: You're using map incorrectly. If you're not using the array that map builds and returns, there's no reason to use it; just use a loop or forEach. More in my post here. And one reason to use an old-fashioned for loop is that you're in control of iteration, which matters because...
However, it's acting weirdly and sometimes I'm getting 2 items being added instead of one.
That's because you're inserting into the array being looped by the map, so on the next pass, it picks up the entry you're adding.
If you do a simple loop, you can easily avoid that by incrementing the index when you insert, or by looping backward; here's the looping backward approach:
const addItemToLevelTwoArray = (uniqueID, arrayID) => {
const reportObject = {
id: arrayID,
title: "",
};
for (const section of data) {
for (let reportIndex = section.content.length - 1; reportIndex >= 0; --reportIndex) {
const report = section.content[reportIndex];
if (report.id === uniqueID) {
section.content.splice(reportIndex, 0, reportObject);
}
}
}
};
Because we're looping backward, we won't pick up the entry we just added on the next pass.
Since the outer loop doesn't have that problem, I used the more convenient for-of.
Since you asked about map, if you do use the array map returns, you can do this by returning an array with the two entries, and then calling flat on the array map builds. (This only works if the array doesn't already contain arrays, because they'll get flattened to.) This is common enough that it's combined in one function: flatMap. It's not what I'd do (I'd do a loop), but it's certainly feasible. Sticking with forEach and flatMap rather than using for-of and for:
const addItemToLevelTwoArray = (uniqueID, arrayID) => {
const reportObject = {
id: arrayID,
title: "",
}
data.forEach(section => {
section.content = section.content.flatMap(report => {
if (report.id === uniqueID) {
// Return the new one and the old one
return [reportObject, report];
}
// Return just the old one
return report;
});
});
};
That assumes it's okay to modify the section object. If it isn't, Alberto Sinigaglia's answer shows creating a new replacement object instead, which is handy in some sitautions.
You can just use flatMap:
const data = [
{
content: [
{
id: 1,
title: "a"
},{
id: 3,
title: "c"
},
]
}
]
const addItemToLevelTwoArray= (uniqueID, arrayID )=> {
const reportObject = {
id:arrayID,
title:'',
}
return data.map(section=> {
return {
...section,
content: section.content.flatMap( report =>
report.id === uniqueID
? [reportObject, report]
: report
)
}
}
)
}
console.log(addItemToLevelTwoArray(3, 2))
The following will extend the inner array .contentwithout modifying the original array data:
const data = [ {id: 0,title:'main',content:[{id:1,title:'Hello'},
{id:2,title:"World"}] } ];
const addItemToLevelTwoArray= (uniqueID, arrayID )=> {
const reportObject = {
id:arrayID,
title:'something new!',
}
return data.map(d=>(
{...d, content:d.content.reduce((acc, rep)=>{
if(rep.id===uniqueID) acc.push(reportObject);
acc.push(rep)
return acc;
},[]) // end of .reduce()
})); // end of .map()
}
const res=addItemToLevelTwoArray(1,123);
console.log(res);

Javascript's method forEach() creates array with undefined keys

I am building a simple todo app, and I'm trying to get the assigned users for each task. But let's say that in my database, for some reason, the tasks id starts at 80, instead of starting at 1, and I have 5 tasks in total.
I wrote the following code to get the relationship between user and task, so I would expect that at the end it should return an array containing 5 keys, each key containing an array with the assigned users id to the specific task.
Problem is that I get an array with 85 keys in total, and the first 80 keys are undefined.
I've tried using .map() instead of .forEach() but I get the same result.
let assignedUsers = new Array();
this.taskLists.forEach(taskList => {
taskList.tasks.forEach(task => {
let taskId = task.id;
assignedUsers[taskId] = [];
task.users.forEach(user => {
if(taskId == user.pivot.task_id) {
assignedUsers[taskId].push(user.pivot.user_id);
}
});
});
});
return assignedUsers;
I assume the issue is at this line, but I don't understand why...
assignedUsers[taskId] = [];
I managed to filter and remove the empty keys from the array using the line below:
assignedUsers = assignedUsers.filter(e => e);
Still, I want to understand why this is happening and if there's any way I could avoid it from happening.
Looking forward to your comments!
If your taskId is not a Number or autoconvertable to a Number, you have to use a Object. assignedUsers = {};
This should work as you want it to. It also uses more of JS features for the sake of readability.
return this.taskLists.reduce((acc, taskList) => {
taskList.tasks.forEach(task => {
const taskId = task.id;
acc[taskId] = task.users.filter(user => taskId == user.pivot.task_id);
});
return acc;
}, []);
But you would probably want to use an object as the array would have "holes" between 0 and all unused indexes.
Your keys are task.id, so if there are undefined keys they must be from an undefined task id. Just skip if task id is falsey. If you expect the task id to possibly be 0, you can make a more specific check for typeof taskId === undefined
this.taskLists.forEach(taskList => {
taskList.tasks.forEach(task => {
let taskId = task.id;
// Skip this task if it doesn't have a defined id
if(!taskId) return;
assignedUsers[taskId] = [];
task.users.forEach(user => {
if(taskId == user.pivot.task_id) {
assignedUsers[taskId].push(user.pivot.user_id);
}
});
});
});

How to get a subarray?

I have a array, with a sub-array, i need to get the sub-array(Task ID), how i can get it?
onDeleteTask(Id: string) {
this._taskService.deleteTask(Id).subscribe(data => {
for (let i = 0; i < this.Notes.length; i++) {
console.log(this.Notes[i].Tasks.filter(this.Notes.findIndex(e => e.Id === Id)));
}
});
}
Data:
[
{
"Id":"099c3d99-8f49-4298-934c-1fc5280d6d84",
"Description":"12312",
"NoteId":"1a91f108-e869-4427-ab5e-09d2262bfe20",
"NoteTitle":"SAKI SAKI 5 DOOLLAA"
},
{
"Id":"e74455d5-5002-4ea3-9f58-653440887690",
"Description":"1",
"NoteId":"1a91f108-e869-4427-ab5e-09d2262bfe20",
"NoteTitle":"SAKI SAKI 5 DOOLLAA"
},
{
"Id":"e75d537e-fe97-4dd5-8ca3-9cce5d3a1827",
"Description":"2",
"NoteId":"1a91f108-e869-4427-ab5e-09d2262bfe20",
"NoteTitle":"SAKI SAKI 5 DOOLLAA"
}
]
I tried this, but id doesn't work
if you just want to get Id for the first occurance then:
this._taskService.deleteTask(Id).subscribe(data => {
for (let i = 0; i < this.Notes.length; i++) {
console.log(this.Notes[i].Tasks[0].Id);
}
});
if you want to get all the Id's then
this.Notes[i].Tasks.map(task => task.Id);
your filter method isn't being called correctly:
this.Notes[i].Tasks.filter(this.Notes.findIndex(e => e.Id === Id))
filter accepts a predicate method to run over each element of the array and should return a boolean, but findIndex returns a number or undefined. You're passing the result of findIndex to filter, so essentially you're doing Tasks.filter(2), which doesn't make sense.
I'd offer a suggestion on how to fix it, but I'm not entirely sure what you are trying to do with the array.

Composing computed properties with other computed properties

I need to write a custom filtered search, sort and paginate properties since my product cannot rely any of the out of box solutions mainly because I need to display images, icons, buttons and urls in my table. Think of it as a product listing page with images and buy links.
My question is how do I chain multiple computed properties?
Example
For Filtered Search:
computed: {
filteredProds:function() {
return this.prodlist.filter(prod => {
return prod.name.toLowerCase().includes(this.search.toLowerCase())
})
}
and for sorting the table I have this computed property along with a method to do the sort.
myprods.sort((a,b) => {
let modifier = 1;
if(this.currentSortDir === 'desc') modifier = -1;
if(a[this.currentSort] < b[this.currentSort]) return -1 * modifier;
if(a[this.currentSort] > b[this.currentSort]) return 1 * modifier;
return 0;
});
As simple as referencing them
data: {
numbers: [1,2,3]
},
computed: {
oddNumbers () {
return this.numbers.filter(n => n % 2)
},
firstOddNumber () {
return this.oddNumbers[0]
}
}

How to check if an Object already exists in an Array before adding it?

I have this algorithme issue, I would like to check if an Object is already present in my Array before adding it.
I tried many different approaches (indexOf, filter...), and my last attempt is with an angular.foreach.
The problem is my $scope.newJoin remains always empty. I understood why, it's because the if is never read, because of the 0 size of my $scope.newJoin, but I don't know how to figure this out...
$scope.newJoinTMP is composed by : 6 Objects, within each a timePosted attribute (used for compare these different array Objects).
$scope.newJoin is an empty Array. I want to fill it with the Objects inside $scope.newJoinTMP but with the certainty to have once each Objects, and not twice the same ($scope.newJoinTMP can have duplicates Objects inside, but $scope.newJoin mustn't).
angular.forEach($scope.newJoinTMP, function(item)
{
angular.forEach($scope.newJoin, function(item2)
{
if (item.timePosted === item2.timePosted)
{
//snap.val().splice(snap.val().pop(item));
console.log("pop");
}
else
{
$scope.newJoin.push(item);
console.log("newJoin :", $scope.newJoin);
}
});
});
if(!$scope.newJoin.find(el=>item.timePosted===el.timePosted){
$scope.newJoin.push(item);
console.log("newJoin :", $scope.newJoin);
}
You dont want to push inside an forEach, as it will push multiple times...
There might be better ways to handle your particular situation but here's a fix for your particular code.
Replaced your inner for each with some which returns boolean for the presence of element and by that boolean value, deciding whether to add element or not
angular.forEach($scope.newJoinTMP, function(item)
{
var isItemPresent = $scope.newJoin.some(function(item2)
{
return item.timePosted === item2.timePosted;
//you dont need this conditional handling for each iteration.
/* if (item.timePosted === item2.timePosted)
{
//snap.val().splice(snap.val().pop(item));
console.log("pop");
}
else
{
$scope.newJoin.push(item);
console.log("newJoin :", $scope.newJoin);
} */
});
if( ! isItemPresent ) {
$scope.newJoin.push(item);
} else {
//do if it was present.
}
});
If you want to avoid the nested loop (forEach, some, indexOf, or whatever) you can use an auxiliar object. It will use more memory but you will spent less time.
let arr = [{ id: 0 }, { id:0 }, { id: 1}];
let aux = {};
const result = arr.reduce((result, el) => {
if (aux[el.id] === undefined) {
aux[el.id] = null;
return [el, ...result];
} else {
return result;
}
}, []);
console.log(result);
You can use reduce
$scope.newJoin = $scope.newJoinTMP.reduce(function(c, o, i) {
var contains = c.some(function(obj) {
return obj.timePosted == o.timePosted;
});
if (!contains) {
c.push(o);
}
return c;
}, []);
The problem with your current code is, if newJoin is empty, nothing will ever get added to it - and if it isnt empty, if the first iteration doesn't match the current item being iterated from newJoinTMP - you're pushing.

Categories