RefactoringifstatementsusingRamda
I'm new to using Ramda so when I performed a map function (in my gatsby app) on an array of blog posts, within that map I used simple if statements. But I want to do this the right way, using Ramda all the way through, but I'm finding its many options overwhelming. I've definitely found and tried many examples from StackOverflow and elsewhere and have been able to grab the categories but not the whole blog post based on those categories. So while I realize there are examples out there I am struggling to apply them correctly in my instance. Any opinions about how I would refactor this code?
const featured_post = [];
const hrt_category = [];
const work_category = [];
const prep_category = [];
const ed_category = [];
{map(
({ node }) => {
if (node.categories.some(e => e.slug === 'featured')) {
featured_post.push(node);
}
if (node.categories.some(e => e.slug === 'hormones')) {
hrt_category.push(node);
}
if (node.categories.some(e => e.slug === 'estrogen')) {
work_category.push(node);
}
if (node.categories.some(e => e.slug === 'prep')) {
prep_category.push(node);
}
if (node.categories.some(e => e.slug === 'ed')) {
ed_category.push(node);
}
},posts
)}
Any help is much appreciated.
回答
To my mind, this is a misuse of Ramda (disclaimer: I'm a Ramda founder.)
Ramda is all about working with immutable data. Although the internals of Ramda functions may mutate local variables, they never alter anything else.
Using map to create side-effects such as pushing to global variables is definitely a mismatch for Ramda.
Besides that, I am not a fan of this repetitive code. My take would be radically different. I wouldn't use all those global collections of posts.
Here's one possibility:
const groupPosts = (names, posts) =>
Object .fromEntries (names .map (name => [
name,
posts .filter (
({node: {categories}}) => categories .some (({slug}) => slug == name)
) .map (({node}) => node)
]))
const posts = [{node: {id: 1, categories: [{slug: 'prep'}, {slug: 'ed'}]}}, {node: {id: 2, categories: [{slug: 'estrogen'}]}}, {node: {id: 3, categories: [{slug: 'ed'}]}}, {node: {id: 4, categories: [{slug: 'hormones'}, {slug: 'prep'}]}}, {node: {id: 5, categories: [{slug: 'featured'}]}}, {node: {id: 6, categories: [{slug: 'prep'}]}}, {node: {id: 7, categories: [{slug: 'estroogen'}]}},]
console .log (JSON .stringify (
groupPosts (['featured', 'hormones', 'estrogen', 'prep', 'ed'], posts)
, null, 2))
.as-console-wrapper {max-height: 100% !important; top: 0}
But if you want those individual subcollections, we could extract from this the function that matches a single set of them:
const matchSlugs = (name) => (posts) =>
posts .filter (
({node: {categories}}) => categories .some (({slug}) => slug == name)
) .map (({node}) => node)
and we would use it like const featured_posts = matchSlugs ('featured') (posts) and our original function would be an easy gloss:
const sortPosts = (names) => (posts) =>
Object .fromEntries (names .map (name => [name, matchSlugs (name) (posts)]))
Note that this has not used Ramda anywhere. We could start changing it to use Ramda's nicer map and filter functions, to use toPairs in place of Object .fromEntries, possibly to use Ramda's any in place of .some. It might make this a bit cleaner, and I would recommend that you try if you're in the process of learning Ramda. But that is the icing. The cake is in simplifying the data structures and the code that use them.
Update
The OP posted a Ramda-based refactoring of matchSlugs and asked for feedback.
Here is a series of refactorings of that version to a fully point-free version:
-
The OP's solution (with my own layout since comments don't allow us to display layout):
const matchSlugs = (name) => (posts) => map ( ({node}) => node, filter( ({node: {categories}}) => any ((({slug}) => slug == name)) (categories), posts ) ); -
After pulling out the
postsparameter:const matchSlugs2 = (name) => pipe ( filter(({node: {categories}}) => any ((({slug}) => slug == name)) (categories)), map (prop ('node')) )This version separates the filtering of existing nodes from the final mapping of the results, and by putting those two steps into a
pipecall, allows us to remove the second argument. Note thatpipetakes some functions and returns a function. This retains the behavior above. -
After cleaning up the destructured parameter in
filter:const matchSlugs3 = (name) => pipe ( filter ( pipe ( path (['node', 'categories']), any (({slug}) => slug == name) ) ), map (prop ('node')) )Here we use
path (['node', 'categories'])to replace the({node: {categories}}parameter. That involves anotherpipecall, which I hope to clean up later. -
After replacing the anonymous function with
propEqconst matchSlugs4 = (name) => pipe ( filter (pipe ( path (['node', 'categories']), any (propEq ('slug', name)) )), map (prop ('node')) )This is just a minor fix, but
propEqreads more cleanly to me than the lambda function. It also will allow us to do the next refactoring. -
After refactoring to remove the
nameparameter:const matchSlugs5 = pipe ( propEq ('slug'), any, flip (o) (path (['node', 'categories'])), filter, o (map (prop ('node'))) )This is the largest step here. We turn this into a point-free function, using two instances of
o, Ramda's curried binary version ofcompose. This is very useful in making functions point-free, but can feel fairly obscure. (The nameois meant to remind us of the mathematical composition sign,?.) The first time we need toflipo, since Ramda does not have apipe-style version ofo.
For some Ramda users, this would be the ultimate goal, achieving a point-free version of the function. There are times when that is quite readable, but I personally feel that this final step has reduced readability. I might go back to an earlier pointed version, or, even better, to a version inspired by this one, but with the points reinstated:
const matchSlugs6 = (name) => (posts) =>
map (
prop('node'),
filter (
compose (any (propEq ('slug', name)), path (['node', 'categories'])),
posts
)
)
To me this version is the most readable of the bunch. But tastes differ, and you may find another one of these or an entirely different version most readable.