Globalizing Redux Selectors

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.

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'

exportdefault(localStateTransform,selectors)=>

map(selector=>compose(selector,localStateTransform),selectors)

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

Using globalizeSelectors

exportdefaultglobalizeSelectors(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'

constglobalize=transform=>selector=>(...args)=>

selector(...adjust(transform,-1,args))

exportdefault(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!