javascriptreduxreselectredux-selector

How to properly use createSelector to memoize a selector which maps over an object


I have this selector:

const selectListOfApplicationIdAndApplicationCompany =
  state => Object.values(state.applications.dict)
    .map((x) => ({
      "id": x.id,
      "companyName": x.companyName
    })
  );

I am getting a warning from React saying that this selector should be memoized because it is returning a different object for same input parameters. I understand why that is happening. I started reading through the Redux documentation about createSelector. I did read their simple examples, but I still don't understand how to memoize the selector I have.

I have tried to do just:

export const selectListOfApplicationIdAndApplicationCompany = createSelector(
  [_selectListOfApplicationIdAndApplicationCompany],
  (data) => data
);

That is returning a warning:

The result function returned its own inputs without modification. e.g createSelector([state => state.todos], todos => todos) This could lead to inefficient memoization and unnecessary re-renders. Ensure transformation logic is in the result function, and extraction logic is in the input selectors.

The documentation talks about "input selectors" and "output selectors". I do not understand what part of my original selector is the input and output, or even if I can break that selector into two parts.


Solution

  • The result function returned its own inputs without modification. e.g createSelector([state => state.todos], todos => todos)

    This is just informing you of an anti-pattern, the created selector doesn't do anything different that just using the state => state.todos selector function directly, and can under certain circumstances lead to poor performance.

    The documentation talks about "input selectors" and "output selectors". I do not understand what part of my original selector is the input and output, or even if I can break that selector into two parts.

    Given the following:

    const _selectListOfApplicationIdAndApplicationCompany =
      state => Object.values(state.applications.dict)
        .map((x) => ({
          "id": x.id,
          "companyName": x.companyName
        })
      );
    
    export const selectListOfApplicationIdAndApplicationCompany = createSelector(
      [_selectListOfApplicationIdAndApplicationCompany],
      (data) => data
    );
    

    The first argument passed to createSelector, the array of selectors, are the input selector(s), e.g. _selectListOfApplicationIdAndApplicationCompany and any other selectors in the array, and the last argument is the output, e.g. the result function that computes a value to memoize.

    Ensure transformation logic is in the result function, and extraction logic is in the input selectors.

    This means the input selectors should select state or the output from another selector, and the result function should do the work, e.g. the computing of a new value given the inputs.

    Update the code to something like the following:

    export const selectApplicationsDict = state => state.applications.dict;
    
    export const selectListOfApplicationIdAndApplicationCompany = createSelector(
      [selectApplicationsDict],
      (applicationsDict) => Object.values(applicationsDict)
        .map(({ companyName, id }) => ({ companyName, id }),
    );
    

    or

    export const selectListOfApplicationIdAndApplicationCompany = createSelector(
      [state => state.applications.dict],
      (applicationsDict) => Object.values(applicationsDict)
        .map(({ companyName, id }) => ({ companyName, id }),
    );
    

    or you can break it down for finer granularity and memoization

    export const selectApplications = state => state.applications;
    
    export const selectApplicationsDict = createSelector(
      [selectApplications],
      (applications) => applications.dict,
    );
    
    export const selectListOfApplicationIdAndApplicationCompany = createSelector(
      [selectApplicationsDict],
      (applicationsDict) => Object.values(applicationsDict)
        .map(({ companyName, id }) => ({ companyName, id }),
    );
    

    The basic gist of the memoized selectors and the inputs and outputs is that the defined result function is only called when the inputs change and the output needs to be re-computed and memoized, or it's called for the first time since there is no memoized result.

    The difference between the above two implementations is how new results will be computed.