I have a simple scenario in which I want to access my store.entities
, but only those which are active. I understand the following is bad because a new reference is given each time:
const activeEntities = useSelector(
state => state.entities.filter(entity => entity.active)
);
So my solution is just to move the filtering outside the selector like so:
const activeEntities = useSelector(state => state.entities)
.filter(entity => entity.active);
I'm still new to Redux, but I believe this now means the component will only re-render on change to state.entities
.
My question is: can this made any better at all by memoization?
I understand the following can be written:
const selectActiveEntities = createSelector(
(state) => state.entities.items,
(items) => items.filter(entity => entity.active)
);
But in my mind, this is functionally equivalent to what I wrote in my second example.
In an ideal world, I would like the component to only be re-rendered when there is a change in the active entities (i.e. the underlying values of the filter function change). How can I achieve this otherwise?
You are right about the point that even the third example is equivalent in terms of the rendering it will cause.
How createSelector()
works is that the outputSelector
will not run if the inputSelector
output does not change, but if your state array reference changes, it will run. And in that case, since the output selector is using a .filter()
, a new array will be created even if the active values are still the same.
Since you want to prevent rerender based on the active values you can use a customEquality
function, which is passed in as the second argument of useSelector
.
For example if you have the ids of each of your entities, ensure that you only rerender when they change:
const idCheck = (prevArr,newArr) => {
//create array of ids
const prevArrIds = prevArr.map(({id}) => id);
const newArrIds = newArr.map(({id}) => id);
//sort array
prevArrIds.sort((a,b) => a-b);
newArrIds.sort((a,b) => a-b);
//join the array into a string and compare
return prevArrIds.join('') !== newArrIds.join('')
};
...
const activeEntities = useSelector(state => state.entities.filter(entity => entity.active), idCheck);
Of course the above idCheck
will now always run when the store value changes.
The optional comparison function also enables using something like Lodash's _.isEqual()
or Immutable.js's comparison capabilities. There are is shallowEqual
from react-redux
.
A minor optimization you can do is use the selector created from createSelector and then still use this custom equality method.