Calculate inside map in es6 - reactjs

I need to calculate the total for a specific field inside map
{agent.reviews.map((a, i) => {
return (
<p style={{fontSize: 15}} key={a.id}>Knowledge Rating:
{a.local_knowledge_rating}</p>
)
})}
Returns
Knowledge Rating: 1
Knowledge Rating: 2
But I need to achieved is to get the total instead
Knowledge Rating: 3
Any ideas?
"users": [
{
"name": "someee",
"reviews": [
{
"id": 1,
"local_knowledge_rating": 1,
},
{
"id": 2,
"local_knowledge_rating": 2,
}
],
}
]

You can do something like below
Declare below method outside render and pass reviews array to it as param
The below approach will sum the rating for every user
sumRating = reviews => {
let total = 0;
agent.reviews.forEach(a => {
total+= a.local_knowledge_rating;
});
return total;
}
<p style={{fontSize: 15}}>Knowledge Rating: {this.sumRating()}</p>
There are many other ways to implement the same. The above approach is one of them

you can try sumBy with lodash: https://lodash.com/docs/4.17.11#sumBy

renderKnowledgeRating() {
const sum = agent.reviews.reduce(function(a, b) { return a. local_knowledge_rating + b. local_knowledge_rating; }, 0)
return (
<p style={{fontSize: 15}}>Knowledge Rating: {sum}</p>
)
}
{this.renderKnowledgeRating()}
To enhance readability, extract the portion out to another method renderKnowledgeRating. and instead of using map which not really helping in your situation, handle the calculation differently, and return a single component

As suggested try this:
function getSum(total, num) {
return total + num.local_knowledge_rating;
}
{agent.reviews.map((a, i) => {
return (
<p style={{fontSize: 15}} key={a.id}>Knowledge Rating:
{a.reduce(getSum)}</p>
)
})}

Related

Converting async responce of Array of Arrays into Object

I am trying to convert Array of Arrays that I get from Observable into single array of objects. This is my code:
this.jobsService.jobsUpdated.pipe(
map(
(jobsData)=> {
return jobsData.map(
(job)=>{
return job.parts.map((part)=>{ return part })
}
)
}
)
).subscribe(
(partsData)=>{
console.log('partsdata', partsData)
}
);
This is the data format I am getting back:
[
[
{partName:1, number:10, quantity: 100}
],
[
{partName:2, number:20, quantity: 200},
{partName:3, number:30, quantity: 300}
],
etc...
]
Please help to convert this data, hopefully in the pipe method, into :
[
{partName:1, number:10, quantity: 100},
{partName:2, number:20, quantity: 200},
{partName:3, number:30, quantity: 300}
]
Use reduce and no need of using inner map
Why reduce? - To generate single output based on multiple inputs.
this.jobsService.jobsUpdated.pipe(
map((jobsData)=> {
return jobsData.reduce(
(acc, job)=>{
acc.push(...job.parts)
return acc;
}
, [])
})
);
Alternatively concat can also be used
this.jobsService.jobsUpdated.pipe(
(jobsData)=> jobsData.reduce(
(acc, job)=> acc.concat(job.parts), [])
})
);
If you just want a flat array:
this.jobsService.jobsUpdated
.pipe(
pluck('parts'),
map((parts)=> parts.flat())
)

Filtering JSON object to get the average of 2 objects

I'm doing an React assignment for school but I'm a bit stuck and I can't find the right answer.
I have a data file with the following data:
const students = [
{
"name": "Evelyn",
"assignment": "SCRUM",
"difficultyRating": 3,
"funRating": 4
},
{
"name": "Evelyn",
"assignment": "W1D1-1",
"difficultyRating": 3,
"funRating": 3
},
{
"name": "Evelyn",
"assignment": "W1D2-1",
"difficultyRating": 1,
"funRating": 3
}
]
This goes on, there are 10 student and different assignments. What I need to do is get the average of the difficultyRating and funRating per assignment and use this data in a Victory Graph to display. Victory is working but it's not displaying the average.
I have this function already which takes all the assignments and fills it with the other data but I don't know what to do in the next step, how can I make a new Array of objects that I can use in the Victory Chart which displays the average difficulty/funrating per assignment.
The code I have so far is this:
const groupByAssignment = (objectArray, property) => {
return objectArray.reduce(function (total, obj) {
let key = obj[property];
if (!total[key]) {
total[key] = [];
}
total[key].push(obj);
return total;
}, {});
}
let groupedAssignments = groupByAssignment(students, 'assignment');
In the Victory Graph the output looks like this now:
<VictoryBar
style={{
data: {
fill: "#ff0b03",
},
}}
barWidth={2}
data={props.data}
x="assignment"
y="difficultyRating"
/>
)}
What I need is a data piece that has every assignment with the difficulty/rating averaged from all 10 students.
The following will give the average values of fun rating and difficulty rating. I have added more entries as there was only one record for each assignment.
const students = [ { name: "Evelyn", assignment: "SCRUM", difficultyRating: 3, funRating: 4, }, { name: "Pqr", assignment: "SCRUM", difficultyRating: 4, funRating: 2, }, { name: "Evelyn", assignment: "W1D1-1", difficultyRating: 3, funRating: 3, }, { name: "Evelyn", assignment: "W1D2-1", difficultyRating: 1, funRating: 3, }, { name: "Abc", assignment: "W1D2-1", difficultyRating: 5, funRating: 4, }, ];
const groupByAssignmentWithAverage = (objectArray, property) => {
return objectArray.reduce(
(prevValue, { difficultyRating, funRating, [property]: key }) => {
// key is the value of in obj with property as data name
if (!prevValue[key]) {
// create a new entry for each assignment type
prevValue[key] = {
difficultyRatingAvg: difficultyRating,
funRatingAvg: funRating,
count: 1,
};
} else {
// get the previous count and average values
const {count, difficultyRatingAvg, funRatingAvg} = prevValue[key];
prevValue[key] = {
difficultyRatingAvg:
(difficultyRatingAvg + difficultyRating) /
(count + 1),
funRatingAvg: (funRatingAvg + funRating) / (count + 1),
count: count + 1,
};
}
return prevValue;
},
{}
);
};
let output = groupByAssignmentWithAverage(students, "assignment");
console.log(output);
My solution would be to seprate the objects with same assignment name, and then just find the average of that particular assignment by mapping over individual arrays of objects which we get from initial step.
result = students.reduce(function (r, a) {
r[a.assignment] = r[a.assignment] || [];
r[a.assignment].push(a);
return r;
}, Object.create(null));
console.log(result);
Now you can easily format data according to the graph package you are using.

Show specific number of items from array in React JS

I have following code: my code
I need to check if my elements name.length sum count is greater than 25 then only show items whose name.length sum is less than 25. And also print count of items which are not shown.
I am trying something like this, but I stuck, please help :)
const skills = [
{
id: 1,
name: "Html"
},
{
id: 2,
name: "css"
},
{
id: 3,
name: "bootstrap 4"
},
{
id: 4,
name: "scss"
},
{
id: 5,
name: "JS"
},
{
id: 6,
name: "React"
},
{
id: 7,
name: "Jquery"
},
{
id: 8,
name: "Vue JS"
}
];
return (
<div>
{skills.map((skill) => (
<li key={skill.id}>{skill.name}</li>
))}
{/* with this line I am wanting to show each elemet char count */}
{skills.map((skill) => (
<div>{skill.name.length}</div>
))}
{/* need to show hide elements count */}
<div>+{count}</div>
</div>
);
In the end I need to get view like this
Html
css
bootstrap 4
scss
JS
+3
Well you can approach this as:
let lengthCount = 0;
let maxIndex = 0;
skills.map((item, index)=>{
if(lengthCount <= 25 ){
maxIndex = index;
console.log("item: " , item.name)
}
lengthCount = lengthCount + item.name.length;
})
console.log("+",skills.length - maxIndex, " more");
Result:
Here is the link to codepen https://codepen.io/the_only_yasir/pen/dyOdZZR?editors=0010
You can do it like this.
return (
<div>
{skills.map((skill) => {
if (skill.name.length < 25) {
return <li key={skill.id}>{skill.name}</li>;
} else {
countMoreThan25++;
}
})}
+{countMoreThan25}
</div>
);
Here is the code for reference.
https://codesandbox.io/s/jolly-newton-qjxpw?file=/src/App.js:549-799

How to return only top 5 records in Vue js nested loop

I need to iterate over shoppingItem (items) inside shoppingOrder (orders). There are three orders. The first order has one item (itemid:1), second order has six items (itemid:2,3,4,5,6,7), third order has one item (itemid:8). I need to show only the top five items. i.e., 1,2,3,4,5 but the current code only limits the item in the second order, showing five items inside. The final output comes like 1,2,3,4,5,6,8
If first order has five items the loop should exit, if first order has one item and second order has six items, it has to show 1 in order 1 and 2,3,4,5 in order2 and exit before order3. But in my example, using order.shoppingItem.slice(0,5) only limits the items in the second order. It is not limiting the total items. How do I resolve this issue? I am using Vue JS version 2
NestedLoop.vue
<template>
<div>
<div v-for="order in shoppingOrder" :key="order.orderId">
<div v-for="item in order.shoppingItem.slice(0,5)" :key="item.itemId">
{{item.itemId}}
</div>
</div>
</div>
</template>
<script>
export default {
name: "NestedLoop",
data() {
return {
shoppingOrder: [
{
orderId: 1,
orderDate: "7/30/2020",
orderStatus: "Dispatched",
shoppingItem: [
{
itemId: 1,
itemName: "Pen",
itemQuantity: "1",
itemPrice: "10.00"
}
]
},
{
orderId: 2,
orderDate: "7/25/2020",
orderStatus: "Ordered",
shoppingItem: [
{
itemId: 2,
itemName: "Notebook",
itemQuantity: "2",
itemPrice: "40.00"
},
{
itemId: 3,
itemName: "Scale",
itemQuantity: "3",
itemPrice: "100.00"
},
{
itemId: 4,
itemName: "Sharpener",
itemQuantity: "1",
itemPrice: "10.00"
},
{
itemId: 5,
itemName: "DocumentFolder",
itemQuantity: "1",
itemPrice: "10.00"
},
{
itemId: 6,
itemName: "PencilBox",
itemQuantity: "5",
itemPrice: "140.00"
},
{
itemId: 7,
itemName: "SketchBox",
itemQuantity: "5",
itemPrice: "10.00"
}
]
},
{
orderId: 3,
orderDate: "7/34/2020",
orderStatus: "Dispatched",
shoppingItem: [
{
itemId: 8,
itemName: "Sketch",
itemQuantity: "1",
itemPrice: "10.00"
}
]
}
]
};
},
methods: {}
};
</script>
<style scoped>
</style>
Result
1
2
3
4
5
6
8
Expected only to return top 5 items like this (1 in order1 and then 2,3,4,5 in order2),
1
2
3
4
5
I love how succinct #Gaetan C's answer is, but .flat is a relatively new method that will require poly-filling for browsers if you are using Vue-cli 3. See this Github issue for more details. If you run into any challenges, you may need to manually edit the poly-fill option of the babel preset.
For more compatibility among other browsers or if you are using vue cli 2 or below, I present this solution.
computed: {
firstFiveShoppingItems: {
let initialShippingItem = [];
for (const order of this.shoppingOrder) {
if (initialShippingItem.length + order.shoppingItem.length >=5) {
return [...initialShippingItem, ...order.shoppingItem].slice(0, 5);
}
initialShippingItem = [...initialShippingItem, ...order.shoppingItem]
}
return initialShippingItem;
}
}
Then in your template, you add
<div v-for="item in firstFiveShoppingItems" :key="item.itemId">
{{item.itemId}}
</div>
As I said, it's not as elegant or succinct as #Gaetan C's answer but it will be more compatible among other browsers and you won't need to go through headaches with poly-fill configurations.
Another thing I like about my solution is that it doesn't iterate over all shopping item arrays like #Gaetan C's answer. It breaks when the result is obtained.
Just use a computed property instead to get the 5 first shopping items:
computed: {
firstShoppingItems: {
return this.shoppingOrder.map(x => x.shoppingItem).flat().slice(0, 5);
}
}
Then you just need one v-for loop:
<div v-for="item in firstShoppingItems" :key="item.itemId">
{{item.itemId}}
</div>
I'd recommend taking advantage of Vue's computed properties to make a list limited to five items. Currently your code is only slicing the inner list to five items.
computed: {
topFive() {
const limit = 5;
let count = 0;
let res = []
for (let i = 0; i < this.shoppingOrder.length && count < limit; i += 1) {
let order = this.shoppingOrder[i];
for (let j = 0; j < order.shoppingItem.length && count < limit; j += 1) {
let item = order.shoppingItem[j];
res.push(item);
count++;
}
}
return res;
}
Once you have a computed property, you can call it from the template like so:
<div v-for="item in topFive" :key="item.itemId">{{item.itemId}}</div>

RethinkDB - is there a better way of doing this non-atomic update on a nested array?

I have two tables in a RethinkDB database - "leagues" and "players", with document structure as follows:
player
{
id: 1,
name: "Bob",
email: "bob#bob.com"
}
league
{
id: 1,
name: "L1",
standings: [{
"player_id": 1,
"position": 1
},{
"player_id": 2,
"position": 2
}]
}
What I'm trying to achieve is when a player is deleted, there is an obviously simple ReQL query to remove the player:
r.table("players").get(1).delete().run(conn, callback);
But I also need to remove that player from any leagues that they are playing in, and then update the positions of all other players in that league so they become sequential again.
This is the query I have to remove a player with id "2":
r.table("leagues").getAll(2, { index: "player_id" }).update({
standings: r.row("standings").filter(function(standing) {
return standing("player_id").ne(2)
}).map(function(standing) {
return r.branch (
standing("position").gt(
r.table("leagues").getAll(2, { index: "player_id" }).nth(0)("standings").filter(function(s) {
return s("player_id").eq(2)
}).nth(0)("position")
),
standing.merge({ position: standing("position").sub(1) }),
standing
)
})
}, {
nonAtomic: true
})
And this is working, it removes the deleted player, and then shuffles the positions of the remaining players to fill the gap and become sequential again.
My question is, is there a better way? One where I don't have to specify the query as nonAtomic? I can only think that I'd need to do a number of separate queries first, to find the position of the player I want to remove, so that can be passed in as a variable rather than a subquery as I believe it's that part that is making this nonAtomic.
Cheers
I assume the standings always match the position in the array?
If so, you could write it like this:
r.table('leagues').getAll(2, {index: 'player_id'}).update(function(row) {
return {
standings: row('standings').filter(function (standing) {
return standing('player_id').ne(2);
}.map(r.range(), function(standing, index) {
return standing.merge({position: index.add(1)});
})
};
});
If not, you could write it like this:
r.table('leagues').getAll(2, {index: 'player_id'}).update(function(row) {
return row('standings').filter(function (standing) {
return standing('player_id').eq(2);
}).nth(0)('position').do(function(pos) {
return {
standings: row('standings').filter(function (standing) {
return standing('player_id').ne(2);
}).map(function(standing) {
return r.branch(
standing('position').gt(pos),
standing.merge({position: standing('position').sub(1)}),
standing);
})
};
});
});
{
standings: row('standings').filter(function (standing) {
return standing('player_id').ne(2)
}.map(r.range(), function(standing, index) {
return standing.merge({position: index.add(1)});
})
};
})

Resources