-
-
Save json2d/e7f65204cf271063cc7e29dd5f14a0b7 to your computer and use it in GitHub Desktop.
const entryByIdSelector = (state) => state.todos.entrybyId | |
const recentIdsSelector = (state) => state.todos.recentIds | |
const entrySelector = _.memoize( | |
(id) => createSelector( | |
entryByIdSelector, | |
(entryById) => entryById[id] | |
) | |
) | |
const recentSelector = createSelector( | |
recentIdsSelector, | |
function (ids) { | |
return ids.map( id => entrySelector(id)(this.state /* magic */ ) ) | |
} | |
) |
note: in the recentSelector
function, this.state
is the same as the state that becomes the arg for recentIdsSelector
when its called
does this break how memoization w
createSelector
is expected to work?
yes if you use this.state
directly to get the result of your callback function, since you're bypassing the way createSelector
expects you to specify parts state
that should become part of the memo key
so this would be bad:
const recentSelector = createSelector(
recentIdsSelector,
function (ids) {
return ids.map( id => this.state.entryById[id] )
}
)
if this.state.entryById
or any entry in it changed, recentSelector
would not pick up on that.
does this holds true for any usage of
state
at all w/o lettingcreateSelector
know which parts of state your want included in the memo key?
yes, so this is proof why you shouldn't/can't have state
arg in a reselector
one safe way to use state
arg to call a reselector inside another reselector is to include the inner one's memo key as part of the outer one's memo key
const recentSelector = createSelector(
recentIdsSelector,
entryByIdSelector, // this make it memoization work (sorta) when using `entrySelector` below
function (recentIds) {
return recentIds.map( id => entrySelector(id)(this.state ) )
}
)
not perfect because it will sometimes call the selector when it doesnt need to, since the entries themselves are not part of the memo key, just the object they're stored in.
so if any entry changes, even if its not in recentIds
, this selector will execute and rebuild the array of entries
heres a way (with its own tradeoffs) achieving the same result w/o having a state
arg
it parametizes ids
(previously recentIds
) outside of createSelector
, and with a little bit more finesse its able to use createSelector
as intended to memoize
const entriesSelector = _.memoize(
(ids) => createSelector(
...ids.map(id => entrySelector(id)),
(...entries) => entries
)
)
now the kinda ugly part
what we need to do is take the result of recentIdsSelector
and use it as the ids
arg in entriesSelector
.
to do this we'll jump into react-redux
land (where we actually use our reselectors) and use 2 levels of connect
, one around the other.
const mstp = (state) => ( { recentIds: recentIdsSelector(state) } )
const mstp2 = (state, props) => ( { recentEntries: entriesSelector(props.recentIds)(state) } )
let RecentContainer = compose(
connect(mstp),
connect(mstp2)
)(Recent)
this might just be the best way, as there seems to be no way to write the recentSelector
(see prev comments) as a single reselector
connect
acts like the glue for our new selector code, giving us the desired high-level behavior:
recentEntries
will only be recomputed if:state.recentIds
changes- an entry in
state.entryById
with one of those ids changes
in general this is how you could use result of one reselector as the arg of another reselector without breaking memoization that reselect
provides
...or just deal with a potential performance hit when doing it all in one connect
🥇
const mstp = (state, props) => {
const recentIds = recentIdsSelector(state)
const recentEntries = entriesSelector(recentIds)(state)
return { recentEntries }
}
let RecentContainer = connect(mstp)(Recent)
same high-level behavior
does this break how memoization w
createSelector
is expected to work?