The React-Redux documentation provides this example for when a selector is used in multiple ponent instances and depends on the ponent's props.
import React, { useMemo } from 'react'
import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'
const makeNumOfTodosWithIsDoneSelector = () =>
createSelector(
state => state.todos,
(_, isDone) => isDone,
(todos, isDone) => todos.filter(todo => todo.isDone === isDone).length
)
export const TodoCounterForIsDoneValue = ({ isDone }) => {
const selectNumOfTodosWithIsDone = useMemo(
makeNumOfTodosWithIsDoneSelector,
[]
)
const numOfTodosWithIsDoneValue = useSelector(state =>
selectNumOfTodosWithIsDone(state, isDone)
)
return <div>{numOfTodosWithIsDoneValue}</div>
}
export const App = () => {
return (
<>
<span>Number of done todos:</span>
<TodoCounterForIsDoneValue isDone={true} />
<span>Number of unfinished todos:</span>
<TodoCounterForIsDoneValue isDone={false} />
</>
)
}
In the function TodoCounterForIsDoneValue
, why does the author wrap makeNumOfTodosWithIsDoneSelector
with useMemo
? My understanding of createSelector
from reselect
is that it generates a memoized selector, so what is the purpose of "double" memoizing this selector?
The React-Redux documentation provides this example for when a selector is used in multiple ponent instances and depends on the ponent's props.
import React, { useMemo } from 'react'
import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'
const makeNumOfTodosWithIsDoneSelector = () =>
createSelector(
state => state.todos,
(_, isDone) => isDone,
(todos, isDone) => todos.filter(todo => todo.isDone === isDone).length
)
export const TodoCounterForIsDoneValue = ({ isDone }) => {
const selectNumOfTodosWithIsDone = useMemo(
makeNumOfTodosWithIsDoneSelector,
[]
)
const numOfTodosWithIsDoneValue = useSelector(state =>
selectNumOfTodosWithIsDone(state, isDone)
)
return <div>{numOfTodosWithIsDoneValue}</div>
}
export const App = () => {
return (
<>
<span>Number of done todos:</span>
<TodoCounterForIsDoneValue isDone={true} />
<span>Number of unfinished todos:</span>
<TodoCounterForIsDoneValue isDone={false} />
</>
)
}
In the function TodoCounterForIsDoneValue
, why does the author wrap makeNumOfTodosWithIsDoneSelector
with useMemo
? My understanding of createSelector
from reselect
is that it generates a memoized selector, so what is the purpose of "double" memoizing this selector?
- 2 It's a parameterized selector. In the redux doc example they don't pass anything to the selector creator function but you can curry the parameter(s) (see my documentation). When you give each ponent it's own selector then memoization will work. – HMR Commented Jun 18, 2020 at 13:24
- Ahh - your 'Parameterized and Memoized' section perfectly helps me understand it. Thank you! – Jack Damon Commented Jun 18, 2020 at 14:12
2 Answers
Reset to default 5Because each ponent needs its own unique instance of the selector for correct memoization behavior. If many ponents use the same selector instance, and each pass in their own different arguments (such as selectThingById(state, props.itemId)
), the selector will never memoize right. By creating a unique instance per ponent, each selector can pass in its own separate args and get consistent memoization.
Slightly unrelated to the question, but I think in this case, we can use useCallback
to have the same result w/o having an extra layer of function in makeNumOfTodosWithIsDoneSelector
.
import React, { useCallback } from 'react'
import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'
const makeNumOfTodosWithIsDoneSelector =
createSelector(
state => state.todos,
(_, isDone) => isDone,
(todos, isDone) => todos.filter(todo => todo.isDone === isDone).length
)
export const TodoCounterForIsDoneValue = ({ isDone }) => {
const selectNumOfTodosWithIsDone = useCallback(
makeNumOfTodosWithIsDoneSelector,
[]
)
const numOfTodosWithIsDoneValue = useSelector(state =>
selectNumOfTodosWithIsDone(state, isDone)
)
return <div>{numOfTodosWithIsDoneValue}</div>
}
export const App = () => {
return (
<>
<span>Number of done todos:</span>
<TodoCounterForIsDoneValue isDone={true} />
<span>Number of unfinished todos:</span>
<TodoCounterForIsDoneValue isDone={false} />
</>
)
}