Sometimes you dont have access to backend and you want to filter the response from an endpoint based on certain criteria. While trivial on flat arrays, this gets a bit tricky if the property you want to query is deeply nested. This is where Ramda shines.
Say we have a prop.users of the shape:
const users = [
{username: 'bob', age: 30, tags: [{name: 'work', id: 1}, {name: 'boring', id: 2}]},
{username: 'jim', age: 25, tags: [{name: 'home', id: 3}, {name: 'fun', id: 4}]},
{username: 'jane', age: 30, tags: [{name: 'vacation', id: 5}, {name: 'fun', id: 4}]}
];
if you want to filter by username
or age
it's quite strait forward:
R.filter(R.propEq('username', 'jane'))(data);
things get tricky when you have something like tags
and you want to filter all users with name: 'fun'
this is the solution:
const hasFunTag = R.any(R.propEq('name', 'fun'))
R.filter(R.compose(hasFunTag, R.prop('tags')))(users)
my first approach to solve this was:
R.filter(R.where({tags: R.contains({name:'fun'})}))(data);
which didn't work because the tags
array can contain other properties like id
in our case.
this is consistent with REST APIs responses.
you could solve it by providing the id
too:
R.filter(R.where({tags: R.contains({name:'fun', id: 4})}))(data);
but most times you only know name
and if you want to write a function and provide the filter as argument, you are stuck.
brains tend to jump to solutions by resorting to what they already know. I knew i could solve my problem if i removed id
from the array and that was something i knew how to do:
const changeTags = (tag) => R.project(['name'], tag)
const extract = users.map(user => ({...user, tags: changeTags(user.tags)}))
R.filter(R.where({tags: R.contains({name:'fun'})}))(extract);
Let's see what happens here:
R.project
helps you pick the properties you need from the given tag
object.tags
based on function above.It solves the querying issue i had but it also transforms the initial users
array and if your code uses somehow the id
in the tags array or you typed your tags
array, you are stuck.
this led me to think the brain needed to learn something new. I asked ramda and FP master @asharif and my React Vienna mentor to help me out of my mental paradigm. I asked him if ramda can solve this without modifying the initial array. 15' later here comes the other way of thinking my brain needed.
const hasFunTag = R.any(R.propEq('name', 'fun'))
R.filter(R.compose(hasFunTag, R.prop('tags')))(users)
it seems like all we needed was a R.any
and a R.compose
in the mix.
I'm gonna try an explanation:
R.filter
takes the users
array and looks at each object inside.R.compose
in ramda executes from right to left sohasFunTag
function should look. R.prop('tags', data[0])
lets say, returns [{"id": 1, "name": "work"}, {"id": 2, "name": "boring"}]
.R.any(R.propEq('name', 'fun'))(of above [] result)
returns either true
or false
depending on whether there is a name
key with value fun
. For the above array it returns false. R.any
helps us out of the 'removing keys before checking' issue i talked about before.users
array.This opens up the possibility for further abstraction of course. I'd like to explore that in the future.