Redux + Ramda: Let’s Code a Higher-Order “Duck”

Yazeed Bzadough
Frontend Weekly
Published in
5 min readFeb 25, 2018
I made this <img src=“redux”/> <h1>+</h1> <img src=”ramda” />

My last two posts were on Higher-Order Ducks. We built one, then refactored it with the createReducer helper.

As promised in the latter, it’s Ramda time. If you spot a better way to implement something please let me know! ❤️

What Is It?

Official site

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

R.append docs:

Returns a new list containing the contents of the given list, followed by the given element.

R.concat docs:

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.

Please clap/comment if you enjoyed! Until next time!

Take care,
Yazeed Bzadough

--

--