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

104 views Asked by At

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);

enter image description here

3

There are 3 answers

0
Scott Sauyet On BEST ANSWER

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.

0
Leon Gaban On

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!

enter image description here


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;
};
1
kevin ternet On

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);