I'm new to reselect and I understand the need. I think it's awesome. However, in my case it kind-of seems like it adds a lot of extra code for no reason. Maybe I'm doing it wrong.
Previous Component:
const mapStateToProps = (state) => {
return {
day: state.filters.day,
minDate: state.filters.minDate,
maxDate: state.filters.maxDate,
};
};
Now Selectors:
import { createSelector } from 'reselect';
const getDay = state => state.filters.day;
export const makeGetDay = () => createSelector(
getDay,
day => day,
);
const getMinDate = state => state.filters.minDate;
export const makeGetMinDate = () => createSelector(
getMinDate,
date => date,
);
const getMaxDate = state => state.filters.maxDate;
export const makeGetMaxDate = () => createSelector(
getMaxDate,
date => date,
);
Now Component:
const makeMapStateToProps = () => {
const getDay = makeGetDay();
const getMinDate = makeGetMinDate();
const getMaxDate = makeGetMaxDate();
return state => ({
day: getDay(state),
minDate: getMinDate(state),
maxDate: getMaxDate(state),
});
};
To clarify, the code works, I just don't understand what Reselect adds in this case..
In the cases you have specified in your question Reselect
does not actually add any value.
The reason is that connect
provided by react-redux
will do it's own shallow compare of the props provided in your mapStateToProps
function to determine if a render is required. In the examples you provided if the values of day
, minDate
or maxDate
do not change you will not be wasting any time with unnecessary renders.
The real value of Reselect
comes in when your selector returns something that is computed.
To borrow an example from Vlad.
Reselect
is great for composing selectors so your selectors may look like this:
export const getDay = state => state.filters.day;
export const getMinDate = state => state.filters.minDate;
export const getMaxDate = state => state.filters.maxDate;
export const getIsDateOutOfRange = createSelector(
getDay,
getMinDate,
getMaxDate,
(day, minDate, maxDate) => day > maxDate || day < minDate
);
And your mapStateToProps
function may look like this:
const mapStateToProps = state => ({
isOutOfRange: getIsDateOutOfRange(state)
});
In this case Reselect
is providing a nice syntax for combining selectors and a marginal performance benefit from the fact that getIsDateOutOfRange
will only be re-computed if one of it's dependant selectors returns a different value.
And there in lies the hidden performance benefit of Reselect
.
If you have a selector that returns a computed array or an object then two identical values returned from the selector will not pass a shallow equality check that Reselect
or connect
will use for memoization purposes.
[0, 1] === [0, 1] // false
So for a contrived example:
export const getDays = state => state.filters.days;
export const getMinDate = state => state.filters.minDate;
export const getMaxDate = state => state.filters.maxDate;
export const getDaysWithinRangeNotPerformant = state => {
const days = getDays(state);
const minDate = getMinDate(state);
const maxDate = getMaxDate(state);
return days.filter(day => day > minDate && day < maxDate);
};
export const getDaysWithinRangePerformant = createSelector(
getDays,
getMinDate,
getMaxDate,
(days, minDate, maxDate) =>
days.filter(day => day > minDate && day < maxDate)
);
The performance benefit that Reselect
unlocks here is two-fold.
First even with multiple calls to getDaysWithinRangePerformant
the possibly expensive filter
is only performed if the actual parameters have changed.
Secondly, and most importantly, every time getDaysWithinRangeNotPerformant
is called by connect
it will return a new array which means that the shallow compare of the prop in connect
will be false and render
will be called again even if the actual days themselves have not changed. Because getDaysWithinRangePerformant
is memoized by createSelector
it will return the exact same array instance if the values have not changed and therefore the shallow compare of props in connect
will be true and it will be able to perform it's own optimisations and avoid an unnecessary render.
And that in my opinion is the big benefit that Reselect
provides.