Skip to content

Instantly share code, notes, and snippets.

@json2d
Last active February 9, 2020 03:56
Show Gist options
  • Save json2d/e7f65204cf271063cc7e29dd5f14a0b7 to your computer and use it in GitHub Desktop.
Save json2d/e7f65204cf271063cc7e29dd5f14a0b7 to your computer and use it in GitHub Desktop.
proposal to put state into callback scope through 'this'
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 */ ) )
}
)
@json2d
Copy link
Author

json2d commented Jun 29, 2019

note: in the recentSelector function, this.state is the same as the state that becomes the arg for recentIdsSelector when its called

@json2d
Copy link
Author

json2d commented Jun 29, 2019

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 letting createSelector 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

@json2d
Copy link
Author

json2d commented Jun 29, 2019

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

@json2d
Copy link
Author

json2d commented Jun 29, 2019

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

@json2d
Copy link
Author

json2d commented Jun 29, 2019

...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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment