Let's say I have a selector like this:
export const selectItemById = (state, id) => state.items[id]
Normally, I would be able to use it in my component like so:
const item = useSelector(state => selectItemById(state, id))
But what if I have a list of items that I want to grab?
const itemIds = [101, 105, 192, 204]
const items = useSelector(state => itemIds.map(id => selectItemById(state, id))
I can't do this because it gives me a warning/error in the console:
Selector unknown returned a different result when called with the same parameters. This can lead to unnecessary rerenders.
Selectors that return a new reference (such as an object or an array) should be memoized: https://redux.js.org/usage/deriving-data-selectors#optimizing-selectors-with-memoization
Is there a better way to iterate over a list and repeatedly call a selector?
Because you are computing and returning a new array from the useSelector
hook each time it's called I suspect you just need to pass an equality comparator to the hook to help the useSelector
hook check the array elements shallowly.
See Equality Comparisons and Updates for details.
import { shallowEqual, useSelector } from 'react-redux';
...
const itemIds = [101, 105, 192, 204];
const items = useSelector(
state => itemIds.map(id => selectItemById(state, id)),
shallowEqual
);
I suggest also creating another selector function that consumes the array of ids and computes the result array internally:
export const selectItemsById =
(state, itemIds) => itemIds.map(id => state.items[id]);
const itemIds = [101, 105, 192, 204];
const items = useSelector(
state => selectItemsById(state, itemIds),
shallowEqual
);
Following from the above example, you can also create a memoized selector function using Reselect (re-exported by Redux-Toolkit as well) to further reduce unnecessary re-renders, i.e. if state.items
doesn't change then the selectItemsById
value doesn't change and the useSelector
hook won't trigger the component to re-render:
import { createSelector } from "reselect"; // or "@reduxjs/toolkit"
const selectItems = state => state.items;
export const selectItemsById = createSelector(
[
selectItems,
(state, itemIds) => itemIds,
],
(items, itemIds) => itemIds.map(id => items[id])
);
const itemIds = React.useMemo(() => [101, 105, 192, 204], []);
const items = useSelector(
state => selectItemsById(state, itemIds),
shallowEqual
);