My last two posts were on Higher-Order Ducks. We built one then refactored it with the createReducer
helper.
Now it’s Ramda time. If you spot a better way to implement something please let me know! ❤️
What Is It?
Ramda’s a library that makes functional programming in JavaScript easier. Many of our hand-written Redux patterns are easily expressed in one or two of Ramda’s functions.
I’m going to purposely overuse Ramda to show off its power. I wouldn’t necessarily do everything in this post, but developing an eye for these patterns never hurts.
Show Me the Code
If you read the previous article, you remember us refactoring the reducer to this:
While I’m much happier with the code after putting it through createReducer
, Ramda will provide an extra kick. Let’s start from the reset
case and work our way down.
actionTypes.reset
() => initialState;
All reset
does is return the initial state. How can Ramda help?
R.always(initialState);
We take initialState
and stuff it into R.always
.
From the Ramda docs:
Returns a function that always returns the given value.
Instead of an arrow function, we have a nice R.always
function. It might be overkill but I’m purposely exaggerating to demo some of Ramda’s arsenal.
actionTypes.addOne
(state, { item }) => [...state, item];
Merge state
(a list) with a given item
.
(state, { item }) => R.append(item, state)
// or
(state, { item }) => R.concat(state, [item])
Returns a new list containing the contents of the given list, followed by the given element.
Returns the result of concatenating the given lists or strings.
Use R.append
to create a new list with item
at the end, or turn item
into an array and concatenate it to state
with R.concat
.
actionTypes.addMany
(state, { items }) => [...state, ...items];
This time, items
is already a list and we want to merge it with state
.
We already know R.concat
does the trick.
(state, { items }) => R.concat(state, items);
Here’s the code so far.
actionTypes.removeOne
(state, { oldItem }) => state.filter((item) => (
!findItemById(oldItem.id)(item)
)),
Recall that we defined findItemById
as:
findItemById = (id) => (item) => item.id === id;
If findItemById(id)(item)
returns false
, keep the item, otherwise remove it. Let’s use it to recap:
See? We passed foods
to the reducer and removeOne
with id: 1
. findItemById
returned true
for our mango
so it was removed.
This has many translations in Ramda.
You can replace state.filter
with R.filter
:
Instead of the logical not operator, !
, we can use R.not
.
We can hide the item
parameter with R.complement
.
Now we’re getting saucy. R.complement
will pass item
to findItemById(oldItem.id)
and return the opposite of whatever it returns. If findItemById(1)(item)
returns true
, R.complement
will return false
.
It’d be nice, however, to avoid negating findItemById
altogether. Ever heard of R.reject
?
Instead of findItemById
’s complement, we can use filter
’s complement–R.reject
!
Boom!
This means when findItemById(id)(item)
returns true
, R.reject
will exclude item
instead of including it like filter
would. The hard work’s done for us, making our code much simpler.
actionTypes.updateOne
Ramda has R.map
. Let’s start with that.
But that’s not even updateOne
’s final form: introducing R.when
.
Summarizing the docs, R.when
takes three parameters: predicate
, whenTrueFn
, and your test object, x
.
If predicate
returns true
, you get back whenTrueFn(x)
.
Else, you get back just x
.
Simple example, let’s increment a number only if it’s 1.
In updateOne
’s case, we used R.when
to encapsulate our previous ternary logic. If findItemById(id)(item)
finds a match and returns true
, then we’ll return newItem
. Otherwise we’ll just return the item
we’ve been given.
actionTypes.set
[actionTypes.set]: (_, { items }) => items
This is so basic, I have no good ideas. So here’s a bad one.
[actionTypes.set]: R.pipe(
R.nthArg(1),
R.prop('items')
)
As my old boss would say: Lol.
Through the power of R.pipe
, we return the second argument’s items
property. That’s all I got for ya…
If you’re interested in how pipe/compose work, see my article on it. Until next time!