typescriptreact-reduxstatememoization

createSelector difference for the memoization values


I have the following problem , in the code I found many places where the createSelector was not implemented correctly and i want to change it to imporve performance in the frontend.

Originally the code was like this

export const foundingListSelector= createSelector(
  (state: RootState) => state.procedureParameters.currentProcedureParameterDTO?.foundingList,
  (foundingList) => foundingList
)

which is obviously wrong because its not memoised. Also i want to note that the procedureParameters is huge... Maybe thats why everything is so laggy.

I can't decide between the following implementations. Which one is better and what is their difference if any.

can i just put it in []

(1)

export const foundingListSelector= createSelector(
  [(state: RootState) => state.procedureParameters.currentProcedureParameterDTO?.foundingList],
  (foundingList) => foundingList
)

and then there is also this

(2)

export const foundingListSelector= createSelector(
  [(state: RootState) => state] ,
  (state ) => state.procedureParameters.currentProcedureParameterDTO?.foundingList
)

I might be wrong but i think the (2) will memoize the RootState , which is also bad because there are a lot of things in there and it would be not ideal, when something changes there to rerun the selector.


Solution

  • export const foundingListSelector= createSelector(
      [(state: RootState) => state.procedureParameters.currentProcedureParameterDTO?.foundingList],
      (foundingList) => foundingList
    )
    

    (1) is functionally equivalent to the current code you are trying to fix. There are two "syntaxes" for createSelector. The first is where all but the last argument are treated as input selector functions and the last arg is the result function that is memoized. The second is where the first argument is an array of input selectors and the second argument is the result function that is memoized.

    export const foundingListSelector= createSelector(
      [(state: RootState) => state] ,
      (state ) => state.procedureParameters.currentProcedureParameterDTO?.foundingList
    )
    

    (2) is technically correct, but as you point out may struggle because it's selecting too much. It's not memoizing the root state though, that's just the input, the returned value from the result function is what is memoized. The issue here is that any time the root state updates, which is likely a lot (!!), the result function will recompute its value. Here that doesn't appear likely to be much of an issue since you are just selecting and returning some nested state, but you can imagine hopefully a scenario where you are iterating a large array or object and have some "computationally expensive" call that takes some time. You don't want to run this more often than is necessary.

    I think typically you'd not want to select more than 1, or maybe 2, levels of depth into any object you are working with. My suggestion would be to split this single selector function up into several selectors that each select or compute and memoize a single value.

    Example:

    // root procedureParameters selector
    export const selectProcedureParameters =
      (state: RootState) => state.procedureParameters;
    
    export const selectCurrentProcedureParameterDTO = createSelector(
      [selectProcedureParameters],
      (procedureParameters) => procedureParameters.currentProcedureParameterDTO,
    );
    
    export const selectFoundingList = createSelector(
      [selectCurrentProcedureParameterDTO] ,
      (currentProcedureParameterDTO) => currentProcedureParameterDTO?.foundingList
    )
    

    Now the selectCurrentProcedureParameterDTO selector function only recomputes when state.procedureParameters updates, and selectFoundingList only recomputes when selectCurrentProcedureParameterDTO selector recomputed and memoized an updated value. The root state can be updated any number of times around these states/selectors and leave them unaffected.