Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using useSelector when iterating over a list

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?

like image 246
noblerare Avatar asked Oct 30 '25 08:10

noblerare


1 Answers

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
);
like image 195
Drew Reese Avatar answered Oct 31 '25 22:10

Drew Reese