2

I have the following selectors setup

selectors.js

const getNodeHistory = (state) => state.session.nodeHistory;
const getUnit = (state, unit) => unit;

export const selectNodeHistory = createSelector(
    [getNodeHistory, getUnit],
    (history, unit) => history.filter((h) => h.unit === unit)
);

Component

const nodeHistory = useSelector((state) => selectNodeHistory(state, unit));

However each component that uses selectNodeHistory re-renders any time there is a change to state.session.nodeHistory i.e a new item added or changed, even if that item doesn't belong to the filtered selector.

Not sure if I'm doing something wrong or it's not possible using this method.

subashMahapatra
  • 6,339
  • 1
  • 20
  • 26
dottodot
  • 1,479
  • 1
  • 16
  • 24
  • you are not doing anything wrong. The filter is causing a newly referenced array to be returned whether the items are different or not, which causes a rerender. This is not something reselect is able to solve, and unfortunately, I'm not sure what the solution to this is. – brietsparks Apr 13 '20 at 07:38

1 Answers1

0

I would make a selector creator that will curry the unit parameter. I am assuming that unit comes from props and the component wants to re calculate if unit changes or history changes:

const getNodeHistory = (state) => state.session.nodeHistory;

export const createSelectNodeHistory = (unit) =>// curry unit parameter
  createSelector([getNodeHistory], (history) =>
    history.filter((h) => h.unit === unit)
  );

const Component = ({ unit }) => {
  //memoize the selector (re create when unit changes)
  //  here memoization of unit parameter is done in component
  //  so each component has their own memoized selector
  const selectNodeHistory = React.useMemo(
    () => createSelectNodeHistory(unit),
    [unit]
  );
  const nodeHistory = useSelector(selectNodeHistory);
};

In that example each component will have their own selector. You could also move the memoization logic to the code creating the selector but if you render multiple components with different unit values using this selector then nothing will be memoized because they all share the same selector.

//moved memoizing to create selector, memoization or unit is shared
//  for all components calling createSelectNodeHistory
export const createSelectNodeHistory = createSelector(
  (unit) => unit,
  (unit) =>
    createSelector([getNodeHistory], (history) =>
      history.filter((h) => h.unit === unit)
    )
);

const Component = ({ unit }) => {
  //create memoized selector, will be re created when
  //  unit changes. Because memoization of unit is shared by
  //  all components nothing will be memoized when you do
  //  <Component unit={1} /><Component unit={2} />
  const selectNodeHistory = createSelectNodeHistory(unit);
  const nodeHistory = useSelector(selectNodeHistory);
};

Now to address our problem; when nodeHistory changes but that change did not affect history.filter you could memoize the filter result like in this answer in your situation the code would look like this:

const createMemoizedArray = () => {
  const memArray = defaultMemoize((...array) => array);
  return (array) => memArray.apply(null, array);
};

export const createSelectNodeHistory = createSelector(
  (unit) => unit,
  (unit) => {
    const memArray = createMemoizedArray();
    return createSelector([getNodeHistory], (history) =>
      memArray(history.filter((h) => h.unit === unit))
    );
  }
);

Or like this if each component needs their own memomized selector:

export const createSelectNodeHistory = (
  unit // curry unit parameter
) => {
  const memArray = createMemoizedArray();
  return createSelector([getNodeHistory], (history) =>
    memArray(history.filter((h) => h.unit === unit))
  );
};
HMR
  • 37,593
  • 24
  • 91
  • 160