Improve your selectors with dependency injection
If you’ve ever had to deal with derived data in redux, you know those can be kind of a pain once your state is normalized - so you start using reselect.
This leads to some code like this (example adapted from the official reselect docs):
// in your selectors...
import { createSelector } from 'reselect';
const shopItemsSelector = state => state.shop.items;
const taxPercentSelector = state => state.shop.taxPercent;
const subtotalSelector = createSelector(
shopItemsSelector,
items => items.reduce((acc, item) => acc + item.value, 0)
)
const makeTaxSelector = () => createSelector(
[subtotalSelector, taxPercentSelector],
(subtotal, taxPercent) => subtotal * (taxPercent / 100),
);
// and in your container...
const makeMapStateToProps = () => {
const taxSelector = makeTaxSelector()
const mapStateToProps = (state, props) => ({
tax: taxSelector(state, props),
});
return mapStateToProps;
}
The problem there is that once you call taxSelector
, you hava an implicit dependency on how your state is structured - over time, specially if you start creating generic reducers/selectors, this causes some problems both when documenting and when trying to reuse the code.
For instance, a coworker recently realized we were duplicating selector code because in some components our taxPercent
equivalent was indeed in the state, but in others it was in a prop - imagine for instance you wanted to calculate both domestic and international tax rates. Might also be a good use for re-reselect, buts a whole different topic.
So we made a somewhat subtle change in how we write our memoized selectors, and started injecting selector dependencies (but keeping the state as a default):
const shopItemsFromState = state => state.shop.items;
const taxPercentFromState = state => state.shop.taxPercent;
const subtotalFromState = createSelector(
shopItemsFromState,
items => items.reduce((acc, item) => acc + item.value, 0)
)
const makeTaxSelector =
(subtotalSelector = subtotalFromState,
taxPercentSelector = taxPercentSelector) => createSelector(
[subtotalSelector, taxPercentSelector],
(subtotal, taxPercent) => subtotal * (taxPercent / 100),
);
We also created some “utility” selectors to ease the use on the containers:
const constSelector = value => () => value;
const propSelector = prop => (state, props) => props[prop];
So when we are testing a selector, we now do:
const taxSelector = makeTaxSelector(constSelector(42), constSelector(10));
expect(taxSelector()).to.equal(4.2);
Now our selector unit tests don’t break when we modify state structure, and we get to reuse code in more containers that do the same thing in multiple places!