Map and pushing arrays - javascript

I am quite new to React and TypeScript. I have some icons I want to map like this:
const iconLookups =
dataPackNumber1.map(
(e) =>
e.icon_prefix &&
e.icon_name && {
prefix: e.icon_prefix,
iconName: e.icon_name,
},
) as IconLookup[];
Further I have more icons under dataPackNumber2 and dataPackNumber3 that look the same and I would like to map them all at once. Another way I was thinking of was to map them seperately and then to push them into the iconLookups array, but I cant seem to figure out how.
iconLookups.push(
dataPackNumber.map(
(e) =>
e.icon_prefix &&
e.icon_name && {
prefix: e.icon_prefix,
iconName: e.icon_name,
},
) as IconLookup[];)
and
const iconLookups =
dataPackNumber1 && dataPackNumber2 && dataPackNumber3.map(
(e) =>
e.icon_prefix &&
e.icon_name && {
prefix: e.icon_prefix,
iconName: e.icon_name,
},
) as IconLookup[];
The code doesn't provide me errors, but on the UI I can see that only the last provided dataPackNumber will be actually rendered, if I chain them with &&.
Can someone please enlighten me?

.push() will push one element onto the array. In your case, that element is an entire array. Resulting in a structure like this:
[1, 2, [3, 4, 5]]
&& will likely just resolve to the last expression, which in your case is just dataPackNumber3.map(/*...*/).
One way to combine all three is with the spread syntax. Structurally it would be something like:
let result = [...array1, ...array2, ...array3];
So in your case it might be:
const iconLookups = [
...dataPackNumber1,
...dataPackNumber2,
...dataPackNumber3.map(/*...*/)
] as IconLookup[];
Edit: As pointed out in a comment below, .push() can indeed add multiple elements to an array when added as multiple arguments:
arr1.push(3, 4, 5);
Which means the spread syntax can also work there:
const iconLookups = dataPackNumber1.map(/*...*/) as IconLookup[];
iconLookups.push(...dataPackNumber2.map(/*...*/));
iconLookups.push(...dataPackNumber3.map(/*...*/));

Related

Vue/JS object order by date, except if an item has a "pinned" property

I have an object of newsfeed items like below.
[{'story_id':130,'pinned':0,....},{'story_id':131,'pinned':1,....},{'story_id':132,'pinned':0,....},{'story_id':133,'pinned':0,....}]
I need to primarily order the news stories by their story_id DESC. But if a story has the property 'pinned'=1 it needs to be first.
filtered_news_feed: function() {
var list= _.orderBy(this.feed_items, ['story_id'],'desc');
return list;
},
The above works, but how do I do pinned items first, then the rest? For some reason the below completely ignores the story_id
var list= _.orderBy(this.feed_items, ['pinned','story_id'],'desc');
Using Array#sort:
const arr = [ {'story_id':130,'pinned':0}, {'story_id':131,'pinned':1}, {'story_id':132,'pinned':0}, {'story_id':133,'pinned':0} ];
const sorted = arr.sort(
({ story_id: storyIdA, pinned: pinnedA }, { story_id: storyIdB, pinned: pinnedB }) =>
pinnedB - pinnedA || storyIdB - storyIdA
);
console.log(sorted);
You can achieve this results in many ways, one of them is:
filtered_news_feed: function() {
const pinnedItems = this.feed_items.filter(item => item.pinned === 1);
const normalItems = this.feed_items.filter(item => item.pinned === 0);
return [
...pinnedItems,
_.orderBy(normalItems, ['story_id'], 'desc')
];
}
First, separate pinned items from normal items. Then return merged array with pinned items at the beginning.
Note: I used modern features of ES here. You should compile it via babel or other tool.

RxJS - Properly merging two arrays from two seperate $http streams

Currently learning RxJS, I'm not gonna lie, I had difficulties understanding it, especially in terms of "Why do we even want to use if we have promises"
But now I think I made small step further.
I know I should avoid nested subscriptions.
Trying to write as short as possible code to merge two streams result into single variable.
So I have two arrays simulating result of streams, which I want to join.
Fights array should became new obj array inside boxers objects
const boxers = [
{
user_id:1,
first_name:'Lennox ',
last_name:'Lewis',
nationality:"UK"
},
{
user_id:2,
first_name:'Mike',
last_name:'Tyson',
nationality:'USA'
},
{
user_id:3,
first_name:'Riddick',
last_name:'Bowe',
nationality:'USA'
},
];
const fights = [
{
fight_id:1,
user_id:1,
opponnent_id:2,
winner_id:1,
venue:'Memphis Pyramid, Tennessee'
}
]
And then I wrote code:
const boxersWithFights2 = boxersStream.pipe(
flatMap(boxers => {
return fightsStream.pipe(
flatMap(fights => {
boxers.map(boxer => boxer.fights = fights.filter(fight => fight.user_id === boxer.user_id ||fight.opponnent_id === boxer.user_id ))
return boxers;
})
)
}
));
Surprisingly this works as expected.
When I subscribe to boxersWithFights, it console.logs me with properly mapped objects.
So it probably also work when returned from external api, but then I would of course need another map() operator.
My question: Is this code written well? Can it be written to be more clean & elegant ?
I also know I could do that easier with e.g. forkJoin, but I really wanted to test flatMap(mergeMap) operator.
You shouldn't mutate data in the stream but boxer.fights = does it.
Also you can combine the streams together via forkJoin because they don't depend on each other.
Try to use map operator instead:
const boxersWithFights2 = forkJoin([boxersStream, fightsStream]).pipe(
map(([boxers, fights]) => {
return boxers.map(boxer => ({
...boxer,
fights: fights.filter(fight => fight.user_id === boxer.user_id ||fight.opponnent_id === boxer.user_id ),
})),
));

Is there some kind of find method on Arrays that returns [item | undefined] rather than item | undefined?

Here is some code from I project I am working in:
const profile = userdataDocs
.filter(isValidUserdataDocument)
.find((document: ICouchDBDocumentDoc) => document._id === profileId);
if (profile) {
return {
id: hashSensitive(profile._id, environment),
type: profile.type,
creationDate: profile.creationDate,
updatedDate: profile.updatedDate,
entityVersion: profile.entityVersion,
};
}
Here is how I would like to have my code look:
return userdataDocs
.filter(isValidUserdataDocument)
.filter((document: ICouchDBDocumentDoc) => document._id === profileId)
.map((profile: ICouchDBDocumentDoc) => ({
id: hashSensitive(profile._id, environment),
type: profile.type,
creationDate: profile.creationDate,
updatedDate: profile.updatedDate,
entityVersion: profile.entityVersion,
}))
.slice(0, 1);
But I get feedback from the rest of my team that I should not use filter because it will continue searching after having found an item. Premature optimization in mind, but still a pretty valid and popular opinion.
Is there some other array method (or altogether different solution) that I can use to write code the way I want, with 'pipes', without getting the performance penalty of moving from find to filter?
Also let me know if I am an idiot and should let go of the pipe dream (pun intended).
Let me start that I like the first solution. In my opinion, it looks good.
But if you are really desperate for a solution that fulfills your pipe dream
const array = [10, 20, 30];
function singleMapFind(args, fn) {
const currentArray = args[2];
const duplicate = [...currentArray];
currentArray.splice(1, currentArray.length - 1);
return duplicate.find(fn);
}
const modified = array.map((...args) => singleMapFind(args, (e) => e > 20));
I would never use it though. Wish you luck with the PR.

removing objects in an object in an array javascript

I've done some research on this issue. I am trying to manipulate an array of calculated values that looks like this in the console:
{nodeVoltages: Array(11), totalPower: Array(1), xlength: Array(11)}
nodeVoltages: Array(11)
0:48
1:47.71306060387108
2:47.250273223993105
3:46.59686907269243
4:45.71876416434013
5:44.53304242029258
6:42.745236969423615
7:Complex {re: 40.38334500994142, im:1.919295696316476, __ember1513267958317: "ember368"}
8:Complex { re:39.55961661806138, im:3.8933604519196416, __ember1513267958317: "ember369"}
This array is created dynamically through some math that I've come up with so there is no input data that I can give you. I'm trying to make the above array look like this:
{nodeVoltages: Array(11), totalPower: Array(1), xlength: Array(11)}
nodeVoltages: Array(11)
0:48
1:47.71306060387108
2:47.250273223993105
3:46.59686907269243
4:45.71876416434013
5:44.53304242029258
6:42.745236969423615
7:40.38334500994142
8:39.55961661806138
Using mathjs, I was able to evaluate my expressions and dynamically add the values into an array with the array.push command and display them. However, my code breaks once the imaginary values pop up in the results of my array.
How can I remove these imaginary numbers from my array? In other words, I need to remove the "im:" parts of the values when they begin to appear before I push them to the displayed array.
I tried to do this with some code I found from a previous answer to someone else's question (How do I remove a particular element from an array in JavaScript?) splice command like this:
var nodeVoltage2 = parser.eval(expression2);
//checks if there are imaginary values and removes them
if ("im" in nodeVoltage2) {
nodeVoltage2.splice(2,1)
}
//adds value to result array for analysis
nodeVoltages.push(nodeVoltage2);
but it returns in the console that "im is not defined".
Any help is greatly appreciated!
You can use the array map function.
Basically, we loop through the array. If the item has a .re property, we take that value only. If there is no .re property, we keep the value as is.
We can either write that in shorthand, as with result using the ternary operator and arrow function, or we can write it in a slightly more verbose but traditional way, as with resultTwo
let data = [
48
,47.71306060387108
,47.250273223993105
,46.59686907269243
,45.71876416434013
,44.53304242029258
,42.745236969423615
,{re: 40.38334500994142, im:1.919295696316476, __ember1513267958317: "ember368"}
,{ re:39.55961661806138, im:3.8933604519196416, __ember1513267958317: "ember369"}
]
let result = data.map((x) => x && x.re ? x.re : x);
let resultTwo = data.map(function(elem) {
// First, we need to check that the array element is not null / undefined
// We then need to check that it has a property called re that is also not null / undefined
if (elem != null && elem.re != null) {
// Just return the property we're interested in
return elem.re;
} else {
// Return the element as is
return elem;
}
});
console.log(result);
console.log(resultTwo);

How to preload a Ramda curried function with item of an Array?

I have tagsList which has about 20 tags, and termIds which is an array of up to 3 tag ids.
I'm trying to find the tags that match the ids in termIds in the tagsList, then set their borders. Looking to avoid for loops and object-oriented programming in favor of a functional programming solution using Ramda curry.
A tag in tagsList looks like :
{
term: 'hi',
id: 123
}
And termIds could look like [123, 345, 678]
When I find an id that matches, I give that tag a new key border1:true, border2:true etc...
Goal:
There is a list of tags, I have another array of termIds, goal is to see if any of the tags in the tagsList have an id that matches the termIds. If so give it a border1, if there are 2, then the 2nd gets border2 and finally 3 gets border 3.
What I tried first:
const checkId = _.curry((term_id, tag) => {
if (tag.id === term_id) {
console.log('match found!', tag)
}
});
const matchId = checkId(termIds);
const coloredTags = R.map(matchId, tagsList);
console.log('coloredTags', coloredTags)
return tagsList;
However this did not work because I am preloading the entire termIds array into the checkId function. When instead I want to preload it with the individual items.
Next I tried this which I thought would work but getting a strange error:
const matchId = R.forEach(checkId, termIds);
This seems a reasonable approach:
R.map(tag => {
const index = R.indexOf(tag.id, termIds);
return (index > -1) ? R.assoc('border' + (index + 1), true, tag) : tag
})(tagsList);
//=> [
// {id: 123, term: "hi", border1: true},
// {id: 152, term: "ho"},
// {id: 345, term: "hu", border2: true},
// {id: 72, term: "ha"}
// ]
Although it could probably be made points-free with enough effort, it would likely be much less readable.
You can see this in action on the Ramda REPL.
If you want to make this into a reusable function, you can do it like this:
const addBorders = R.curry((terms, tags) => R.map(tag => {
const index = R.indexOf(tag.id, terms);
return (index > -1) ? R.assoc('border' + (index + 1), true, tag) : tag
})(tags))
addBorders(termIds, tagsList)
(The call to curry is a Ramda habit. It means you can call addBorders(termIds) and get back a reusable function that is looking for the tags. If you don't need that, you can skip the curry wrapper.)
This version is also on the Ramda REPL.
I think pure JS is enough to do it without Ramda. You just need a map :
var tagsList = [{term: 'hi', id: 123}, {term: 'ho', id: 152}, {term: 'hu', id: 345}, {term: 'ha', id: 72}];
var termIds = [123, 345, 678];
var i = 1;
var results = tagsList.map(x => {
if (termIds.indexOf(x.id) !== -1) x["border"+ (i++)] = true;
return x;
});
console.log(results);
Ah just figured it out, I had to curry the logic a 2nd time:
const matchId = R.curry((tag, term_id) => {
if (tag.id === Number(term_id)) {
console.log('match found!', tag)
}
});
const curried = R.curry((termIds, tag) => {
return R.map(matchId(tag), termIds);
});
const coloredTags = R.map(curried(termIds), tagsList);
console.log('coloredTags', coloredTags)
return tagsList;
So at the coloredTags line, a tag from tagsLists goes into the curried(termIds). Ramda functions accept params from right to left.
curried(termIds) is already preloaded with the termIds array. So next in the const curried = line, the termIds array and single tag make it in and the tag gets sent along into the next curried function matchId, also the termIds array is placed in the R.map. Ramda list functions accept the Array of data as the right param.
Finally in matchId I can make my check!
UPDATE
So the above answers the question I asked, about how to curry an item from an Array. However it caused a bug in my app. Since the termIds array could hold up to 3 items, the coloredTags R.map will run up to 3 times and create duplicate tags in my tagsList.
So just for completeness this is how I solved my in problem, much simpler and didn't need to use a double curried function.
const setTagColors = (tagsList, state) => {
const setBorder = (tag) => {
if (tag.id === Number(state.term_id_1)) {
tag.border1 = true;
} else if (tag.id === Number(state.term_id_2)) {
tag.border2 = true;
} else if (tag.id === Number(state.term_id_3)) {
tag.border3 = true;
}
return tag;
};
const coloredTags = R.map(setBorder, tagsList);
return state.term_id_1 ? coloredTags : tagsList;
};

Categories