skip to Main Content

I have form like so
enter image description here

It has a parent-child relationship. In the RTK slice, it looks like this:

application

-> personList (array of person objects)

–> assetList (array of asset objects)

Example JSON:

{
 application: {
  personList: [
   {
    id,
    fname, 
    lname,
    personFormStatus: { isValid, isDirty},
    assetFormStatus: { isValid, isDirty},
    assetList: [
     {
      type,
      subtype,
      amount,
     }
    ]
   }
  ]
 }
}

And then I have 2 components, 1 per fieldgroup:

const PersonFormComponent = () => {}

const AssetFormComponent = () => {}

I have created a selector in the appSlice.tsx:

export const selectPeopleUniqueIdAndAssetFormStatus = createSelector(
  (state) => {
    console.log('inside inputSelectors');
    return state.application.personList;
  },
  (personList) => {
    console.log('inside combiner');
    return personList.map((p) => ({
      assetFormStatus: p.assetFormStatus,
      id: p.id,
    }));
  }
);

Inside the AssetFormCompnent which doesn’t memoize, it keeps recalculating (even though the updates are in the Person names in the PersonFormComponent):

const AssetFormComponent = () => {
 const peopleIdsAndFormStatus = useSelector(
      selectPeopleUniqueIsAndAssetFormStatus
  );
 
}

But if I pass equalityFn in AssetFormCompnent then it does memoize (even though the updates are in the Person names in the PersonFormComponent):

const AssetFormComponent = () => {
 const peopleIdsAndFormStatus = useSelector(
      selectPeopleUniqueIsAndAssetFormStatus, (oldv,newv) => _.isEqual(oldv,newv)
  );
 
}

Can you please let me know if this is appropriate to use deepEquality, because I would have thought the createSelector is memoizing it already?

2

Answers


  1. Your code doesn’t appear to be correct, your selectPeopleUniqueIdAndAssetFormStatus selector function isn’t being called. I’m pretty sure your selectPeopleUniqueIsAndAssetFormStatus selector function is correctly memoizing its value, but the problem is the anonymous selector function the UI is using. Since an array is returned the shallow equality function is necessary to get the useSelector hook to work like you are expecting. In fact you don’t need the anonymous selector function, selectPeopleUniqueIsAndAssetFormStatus already expects to consume the Redux state as an argument.

    I suspect you are actually doing something like this:

    const peopleIdsAndFormStatus = useSelector(
      (state: RootState) => selectPeopleUniqueIsAndAssetFormStatus(state)
    );
    

    But this is unnecessary, pass selectPeopleUniqueIsAndAssetFormStatus directly as the selector function:

    const peopleIdsAndFormStatus = useSelector(
      selectPeopleUniqueIsAndAssetFormStatus
    );
    
    Login or Signup to reply.
  2. Doc for Deriving Data with Selectors describes your issue when using an array and map:

    import { createSelector } from 'reselect'
    
    const selectTodoDescriptionsReselect = createSelector(
      [state => state.todos],
      todos => todos.map(todo => todo.text)
    )
    

    Unfortunately, this will recalculate the derived array if any other
    value inside of state.todos changes, such as toggling a todo.completed
    flag. The contents of the derived array are identical, but because the
    input todos array changed, it has to calculate a new output array, and
    that has a new reference.

    First suggested solution in doc is to use proxy-memoize

    The same selector with proxy-memoize might look like:

    import { memoize } from 'proxy-memoize'
    
    const selectTodoDescriptionsProxy = memoize(state =>
      state.todos.map(todo => todo.text)
    )
    

    Unlike Reselect, proxy-memoize can detect that only the todo.text
    fields are being accessed, and will only recalculate the rest if one
    of the todo.text fields changed.

    Documentation lists other alternatives as well.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search