Reputation: 11568
So, I am using redux, and reselect tool createSelector
to memoize my selectors in mapStateToProps
, and it is great.
Now, I have some normalized data in my store, and a container that needs inter-dependant data from the store.
I am doing this, for the moment, and it works:
const getItemId = props => parseInt(props.match.params.itemId, 10)
const containerSelector = createSelector(getItemId, itemId =>
createSelector(
[getUser, getItem(itemId)],
(user, item) =>
createSelector(
[
getFoo(item.fooId),
getBar(item.barId)
],
(foo, bar) => ({ user, item, foo, bar })
)
)
)
const mapStateToProps = (state, ownProps) =>
containerSelector(ownProps)(state)(state)
export default connect(mapStateToProps)(Container)
Question: is there a better way to do this?
Edit:
Thanks to Tho Vu answer, I could made it simpler and memoized, like this:
import { getUser, getItemById, getFooById } from 'selectors'
// Note: getStuffById are selector factories like:
// const getStuffById = id => state => state.stuff.objects[id]
const itemIdSelector = (_, props) => parseInt(props.match.params.itemId, 10)
const itemSelector = (state, props) => {
const itemId = itemIdSelector(state, props)
return getItemById(itemId)(state)
}
const fooSelector = (state, props) => {
const item = itemSelector(state, props)
return item ? getFooById(item.fooId)(state) : null
}
const mapStateToProps = createStructuredSelector({
user: getUser,
itemId: itemIdSelector,
item: itemSelector,
foo: fooSelector
})
But I am still mixed about it? Or maybe it is fine already?
Upvotes: 4
Views: 738
Reputation: 4068
From the documentation of reselect
:
When a selector is connected to a component with connect, the component props are passed as the second argument to the selector
So the first createSelector
layer used for getting itemId
is actually unnecessary, which you can replace with:
const getItem = (state, props) => {
const itemId = parseInt(props.match.params.itemId, 10)
return getItem(itemId)
}
Secondly, createSelector
has following attribute:
Selectors created with createSelector have a cache size of 1. This means they always recalculate when the value of an input-selector changes, as a selector only stores the preceding value of each input-selector.
Which means your custom selector will recalculate with every changes of user
, itemId
, item
, foo
, bar
. It is hard to say about the rest of input-selectors, but I don't see any relationship between user
and the others. Thus you should create a separate selector for only user
.
Also, the purpose of createSelector
is to create a selector with retainable memoization, so nesting them the way you do will cause inside selector to be recreated every time outer ones recalculate.
What I normally do is flatten them like this:
const getItem = (state, props) => {
const itemId = parseInt(props.match.params.itemId, 10)
return getItem(itemId)
}
const userSelector = createSelector(getUser, user => user)
const itemSelector = createSelector(getItem, item => item)
foo
and bar
are more difficult, because in your code, the selectors look for changes in item
, item.fooId
and foo
too recalculate. We still need to nest the selectors, but separation of foo
and bar
is required:
const fooFromItemSelector = createSelector(itemSelector, item => getFoo(item.fooId))
const fooSelector = createSelector(fooFromItemSelector, foo => foo)
and same goes for bar
.
Lastly, you will want to group them together to create containerSelector
:
import { createStructuredSelector } from 'reselect'
const containerSelector = createStructuredSelector({
user: userSelector,
item: itemSelector,
foo: fooSelector,
bar: barSelector
})
Upvotes: 2