Reputation: 727
I am new to Redux and trying to figure out how to take full advantage of it.
When writing a selector for a module of the application, what part of the state tree should be passed to the selector so that the selector is both reusable and modular?
For example, given the code below, what is a good way to write selectModuleItemsById
with a state shape similar to stateShapeExample
?
let stateShapeExample = {
module: {
items: {
firstItemId: {...},
secondItemId: {...},
...
}
}
}
const selectModuleRoot = (state) => state.module;
// First Option: starts from the module root
const selectModuleItemById = (state, id) => state.items[id];
// Second Option: starts from the global root
const selectModuleItemById = (state, id) => state.module.items[id];
// Something Else: ???
const selectItemById = (state, id) => state[id];
Upvotes: 3
Views: 2093
Reputation: 67469
The short answer is that it's pretty tricky.
The best writeup on this that I've seen is Randy Coulman's series of posts on modularizing selectors:
The general summary of that seems to be letting "module reducers" write selectors that know how to pick pieces of data out of their own state, then "globalizing" them at the app level based on where that module/slice is mounted in the state tree. However, since the module probably needs to use the selectors itself, you may have to move the registration / setup process into a separate file to avoid a circular dependency issue.
Upvotes: 3
Reputation: 15472
Selectors, by definition, take in the entire state and return a portion of the state. Anything else is basically just a data utility function.
I use ramda lenses to manage this kind of thing.
Consider a directory structure like this:
store
module
data.js
selectors.js
reducers.js
actions.js
data.js
would export the initial state (in this case, just the initial state for modules
) and ramda lenses that describe where pieces of state are.
import { lensPath } from 'ramda'
export default {
items: {
firstItemId: {...},
secondItemId: {...},
...
}
}
export const itemsLens = lensPath(['module', 'items'])
export const makeItemLens = id => lensPath(['module', 'items', id])
Then, in selectors.js
you import the lenses to select the data from the entire state tree.
import {view} from 'ramda'
import {itemsLens, makeItemLens} from './data.js'
export const selectModuleItems = state => view(itemsLens, state)
export const selectModuleItemById = (state, id) => view(makeItemLens(id), state)
This strategy has a few benefits:
lensPath
with view
enables you to do deep property lookups without risking, Cannot read propery firstItemId of undefined
errors. Other libraries have equivalent functions if ramda aint your thing (lodash, immutable.js, etc).The downside is that it's a bunch of extra boilerplate code, but there's value in being explicit and avoiding magical code IMO.
Having said all that, you should also check out reselect for more advanced selector strategies (something I have yet to play with extensively).
Upvotes: 1