Several weeks ago, I wrote about Globalizing Redux Selectors. At the end of the post, I showed an implementation using Ramda that works for selectors taking any number of arguments. I later discovered that the solution doesn’t allow globalized selectors to be called as curried functions. Let’s fix that.

Refresher

If you have no idea what I’m talking about, I recommend reading the original post and the one it refers to.

As a quick refresher, we’re assuming a Redux application that uses a modular structure. In some contexts, we want to have selectors that work on a local slice of the state tree (localized selectors). In others, we want those same selectors to work on the entire state tree (globalized selectors).

The solution we came up with is to export the localized selectors by name, and then default export an object containing the globalized selectors.

At the end of the previous post, we came up with the following code for creating globalized selectors from local ones:

Generlized globalizeSelectors
import { adjust, map } from 'ramda'
const globalize = transform => selector => (...args) =>
selector(...adjust(transform, -1, args))
export default (localStateTransform, selectors) =>
map(globalize(localStateTransform), selectors)

What’s the Problem?

Since I writing that post, I ran into a case where one of my selectors was curried, and I wanted to call the globalized version of the selector in a curried fashion. It didn’t work.

If you’re not familiar with Ramda’s curry function, see Thinking in Ramda: Partial Application.

Here’s a simple test case that I wrote to illustrate the problem:

globalizeSelectors test
import { curry, prop } from 'ramda'
import globalizeSelectors from '../globalizeSelectors'
describe('globalizeSelectors', () => {
const globalState = {
section: {
numbers: [2, 3, 4]
}
}
const localState = prop('section')
const numberAt = (index, state) => state.numbers[index]
const globalized = globalizeSelectors(localState, {
curriedNumberAt: curry(numberAt)
})
context('with a curried selector', () => {
test('the selector can be called normally', () => {
expect(globalized.curriedNumberAt(2, globalState)).toBe(4)
})
test('the selector can be curried', () => {
expect(globalized.curriedNumberAt(2)(globalState)).toBe(4)
})
})
})

The first test (calling the selector normally) passed, but the second one failed.

Looking at our original code, it’s easy to see why: we’re not currying the globalized selectors. Let’s take care of that:

First Attempt at Currying
import { adjust, curry, map } from 'ramda'
// Doesn't work -- don't do this!
const globalize = transform => selector =>
curry((...args) => selector(...adjust(transform, -1, args)))
export default (localStateTransform, selectors) =>
map(globalize(localStateTransform), selectors)

Hmmm, that didn’t work either. You can’t curry a function that takes a variable number of arguments; in that situation, curry assumes that the arguments you’ve given it are all there are going to be.

What we need is a way to tell curry how many arguments to expect.

Fortunately, Ramda also has curryN that is like curry, but also takes the number of arguments to expect. Perfect!

But how can we know how many arguments to expect? This is a general utility function that is supposed to work with selectors that take any number of arguments. We can’t just hard-code an arbitrary number.

We have the original selector, and it knows how many arguments it needs. We can use Ramda’s length function for that.

That should be everything we need.

Using curryN
import { adjust, curryN, length, map } from 'ramda'
const globalize = transform => selector =>
curryN(length(selector),
(...args) => selector(...adjust(transform, -1, args))
)
export default (localStateTransform, selectors) =>
map(globalize(localStateTransform), selectors)

That works!

This version of globalizeSelectors has handled everything I’ve thrown at it so far. Crisis averted!