In a recent post, Modular Reducers and Selectors, we ended up with each module exporting localized versions of its selectors by name, and an object containing the globalized versions of the selectors as the default export. In that post, we wrote the globalized selector by hand. However, it can be helpful to write a generic utility function that globalizes the selectors for us.

Background

I won’t repeat the entire earlier post here, but the general idea is that we have a Redux application using a modular structure. The selectors for each module are written in terms of that module’s local section of the state tree.

These localized selectors are exported by name and used whenever we have the local state tree, such as in our reducer tests.

In other contexts, such as container components and thunk action creators, we need versions of these selectors that can take the entire state tree.

For those situations, we also default export an object containing globalized versions of the same selectors.

Using Ramda, we ended up with the following example code:

Using ramda
import { compose, prop } from 'ramda'
const localState = prop('todos')
export const allTodos = state => {
// extract all todos from the local state
}
export default {
allTodos: compose(allTodos, localState)
}

If you need more context, please read the original post.

This all works fine, but the more selectors we have, the more we repeat the same pattern over and over again. Let’s see if we can generalize it a bit.

In a recent comment on the original post, datchley, shared their generic approach to globalizing selectors. This is something I’ve done as well, and the technique is worth its own post.

Compose All The Things

The simplest selectors are like allTodos above: They take the state as their only argument. In my current project, all of my selectors were like that until quite recently. Rather than repeating the selector: compose(selector, localState) pattern over and over again, we can write a simple function, globalizeSelectors, that would do that for us:

Simplistic globalizeSelectors
// WARNING: Only works with single-argument selectors; see below
import { compose, map } from 'ramda'
export default (localStateTransform, selectors) =>
map(selector => compose(selector, localStateTransform), selectors)

Now we can change the default export of our selectors file:

Using globalizeSelectors
export default globalizeSelectors(localState, {
allTodos
})

We call globalizeSelectors with our localState transform function and an object of selectors that need to be globalized. The results are identical to the hand-written version above.

But what happens when we have a selector that takes more than one argument?

Multi-Argument Selectors

The solution using compose above only works with single-argument selectors. As soon as we have selectors that take multiple arguments, we need to do more.

In order to come up with a generic version of globalizeSelectors, we need to make an assumption about which of the multiple arguments represents the state.

In datchley’s solution, the assumption is that that state argument comes first. That’s a good choice, because it allows the use of ES6 rest parameters to handle the remaining arguments.

In our case, since we’re using Ramda, it makes more sense for the state argument to be last, as that more closely follows Ramda’s conventions. It does make our function slightly harder to write, though, because we can’t use rest parameters.

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)

Update: This version has a problem. See the end of the post for details.

Here, we create a helper function, globalize, as a curried function. We call it with our localStateTransform, which gives us back a function that takes a selector and returns a new selector. We map that function over all of our selectors.

globalize uses Ramda’s adjust function to apply the local state transform to the last argument, then passes the transformed arguments on to the original selector. Though Ramda’s documentation isn’t explicit about it, adjust and its close cousin update can both take a negative index just like nth does. A negative index works backwards from the end of the array.

This version of globalizeSelectors continues to work for single-argument selectors, so we don’t have to change anything in the rest of our code. But now, we can safely use globalizeSelectors to handle all of our selectors as long as they follow the convention that the state parameter comes last.

Conclusion

globalizeSelectors is a handy utility to add to your toolbelt if you’re following the modular structure approach.

I’d like to thank datchley for sharing their version of this function. That comment made me realize that this topic was worth it’s own post, so thanks for the nudge!

If you’d like to know more about Ramda and using it in your Redux applications, see my Thinking in Ramda series, as well as my earlier post, Using Ramda With Redux.

If you’re not using Ramda, you might prefer datchley’s solution which again, you can find in this comment.

Update!

Since writing this post, I discovered that this solution doesn’t allow globalized selectors to be called in a curried fashion. See Globalizing Curried Selectors for a better implementation.