admin管理员组

文章数量:1316351

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?

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?

Share Improve this question edited Jan 29 at 16:30 Drew Reese 204k18 gold badges240 silver badges271 bronze badges asked Jan 29 at 10:34 kiwikodeskiwikodes 7749 silver badges16 bronze badges 1
  • 2 Hi. reselect selectors are memorized. You can call your selector with useSelector(selectActiveEntries) – DominicSeel Commented Jan 29 at 12:49
Add a comment  | 

2 Answers 2

Reset to default 1

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.

const selectActiveEntities = createSelector(
  (state) => state.entities.items,
  (items) => items.filter(entity => entity.active)
);

This may be functionally equivalent to your first examples doing the work directly in or around the useSelector hook, but I suspect it's already providing the memoized result you want. const activeEntities = useSelector(selectActiveEntities); should be fine.

The only "improvement" I'd suggest is splitting up the selector such that you can memoize intermediate results. I believe it's preferable for selectors to work only 1-level deep when accessing into state properties, so each can be memoized for consumption by UI components or when used as input selector functions to other selectors.

Example:

const selectEntities = state => state.entities;

const selectEntityItems = createSelector(
  [selectEntities],
  entities => entities.items,
);

const selectActiveEntities = createSelector(
  [selectEntityItems],
  (entityItems) => entityItems.filter(entity => entity.active),
);
import { useSelector } from 'react-redux';
import { selectActiveEntities } from '../path/to/selectors';

...

const activeEntities = useSelector(selectActiveEntities);

If you've further need to hint at selector value equality, especially when working with arrays, you should use the shallowEqual utility or custom equality function with useSelector.

See Equality Comparisons and Updates for complete details.

Example:

import { shallowEqual, useSelector } from 'react-redux';
import { selectActiveEntities } from '../path/to/selectors';

...

const activeEntities = useSelector(selectActiveEntities, shallowEqual);

本文标签: javascriptHow to avoid rerender in Redux based on the outputs of filter()Stack Overflow