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.
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.
state.applications
state is updated selectListOfApplicationIdAndApplicationCompany
will likely recompute its value since state.applications
is a new reference regardless if state.applications.dict
is different.state.applications.dict
, so if state.applications
updates but dict
didn't, then selectApplicationsDict
will recompute and see that its result is the same and stop there, selectListOfApplicationIdAndApplicationCompany
's input will not have changed and it won't recompute the derived array value of company names and ids.