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 giventag
object.- use spread operator and change
tags
based on function above. - now we can filter as intended.
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 theusers
array and looks at each object inside.R.compose
in ramda executes from right to left so- We need the path inside the object where
hasFunTag
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 eithertrue
orfalse
depending on whether there is aname
key with valuefun
. For the above array it returns false.R.any
helps us out of the 'removing keys before checking' issue i talked about before.- This response in turn becomes the filter predicate and removes the first object from the
users
array.
This opens up the possibility for further abstraction of course. I'd like to explore that in the future.