Reputation: 22150
I using React/Redux/Reselect.
From the reselect doc, if I have multiple component that will use the selector with different parameters, I need to create one per instance.
const makeGetVisibleTodos = () => {
return createSelector(
[
(state, props) => props.visibilityFilter,
state => state.todos,
],
(visibilityFilter, todos) => {
return todos.filter(td => td.visibility === visibilityFilter)
}
}
)
}
But in my case, the listId
can come from several origins (Eg. props.listId
, props.location.match.listId
, props.location.search.listId
etc.)
So I feel more like writing the following:
const makeGetVisibleTodos = listId => {
return createSelector(
[
state => state.todos,
],
(todos) => {
todos.filter(td => td.listId === listId)
}
}
)
}
and connect it the following way:
connect(
(state, ownProps) => ({
offerer: makeGetVisibleTodos(ownProps.location.match.listId)(state, ownProps),
}),
)
I'm pretty sure it works, but I'm not 100% sure this will memoize correctly:
If two components call makeGetVisibleTodos
with the same listId
, they will have 2 different cache values, right? Which is not what I want ...
How about this?
const makeGetVisibleTodos = listId => {
return createSelector(
state => state.todos,
state => listId,
(todos, listId) => {
todos.filter(td => td.listId === listId)
}
}
)
}
In this case, do makeGetVisibleTodos(ownProps.listId)
and makeGetVisibleTodos(ownProps.match.params.listId)
share the same cache value, when ownProps.listId === ownProps.match.params.listId
?
Or another way to formulate the question is: how to pass extra-parameters that wouldn't (directly) depend on state
and ownProps
but are checked on equality during memoization?
I could also extend ownProps
:
connect(
(state, ownProps) => ({
offerer: makeGetVisibleTodos()(state, Object.assign({}, ownProps, {listId: ownProps.location.match.listId}),
}),
)
But find it super ugly and it's loosing the point...
Upvotes: 1
Views: 2561
Reputation: 10419
As the author of re-reselect (a small wrapper around reselect
) I'd like to point out how the library would solve your use case.
re-reselect
provides selectors which retains memoization across different components out of the box.
import createCachedSelector from 're-reselect';
const getVisibleTodos = createCachedSelector(
state => state.todos,
(state, listId) => listId,
(todos, listId) => {
todos.filter(td => td.listId === listId)
}
}
)(
(todos, listId) => listId, // Create/use a different selector for each different listId
);
Use it like a normal selector (in any different container component) and forget about selectors factories:
const makeMapStateToProps = () => {
const mapStateToProps = (state, props) => {
return {
todos: getVisibleTodos(state, props.params.match.listId),
todos2: getVisibleTodos(state, props.listId),
todos3: getVisibleTodos(state, 'hardcodedListId')
}
}
return mapStateToProps
}
This use case is also described in re-reselect docs.
Upvotes: 2
Reputation: 947
In the example you posted above, memoization will not work correctly because you create a new instance of the reselect selector everytime the mapStateToProps
function is called.
Notice how in the docs the mapStateToProps
function also becomes a factory function (makeMapStateToProps
) and the makeGetVisibleTodos
is called before returning the actual mapStateToProps
function, like so.
const makeMapStateToProps = () => {
const getVisibleTodos = makeGetVisibleTodos()
const mapStateToProps = (state, props) => {
return {
todos: getVisibleTodos(state, props)
}
}
return mapStateToProps
}
Also, you do not pass the props that you use in your selector in the makeGetVisibleTodos()
call, but when actually calling the selector itself.
This would result in something like
const makeGetVisibleTodos = () => {
return createSelector(
state => state.todos,
state => (_, listId), // Here you have access to all the arguments passed
(todos, listId) => {
todos.filter(td => td.listId === listId)
}
}
)
}
Then you can write the selectors like this:
const makeMapStateToProps = () => {
const getVisibleTodos = makeGetVisibleTodos()
const mapStateToProps = (state, props) => {
return {
todos: getVisibleTodos(state, props.params.match.listId),
todos2: getVisibleTodos(state, props.listId),
todos3: getVisibleTodos(state, 'hardcodedListId')
}
}
return mapStateToProps
}
Upvotes: 2
Reputation: 281646
Your method will not do memoization correctly since when you call makeGetVisibleTodos
is always creating a new selector. You can improve on it by writing
const listIdQuerySelector = (state, props) => {
return props.match.params && props.match.params.listId;
};
const todoSelector = createSelector(
[
listIdQuerySelector,
state => state.todos,
],
(listId, todos) => {
todos.filter(td => td.listId === listId)
}
}
)
and use it like
connect(
(state, ownProps) => ({
offerer: makeGetVisibleTodos(state, ownProps),
}),
)
Upvotes: 0