In the beginning
When starting out with Redux, reselect is recommended ad-hoc to derive data from state. This is the right idea, but it seems to be easy to just take the humble selector for granted, expecting it to handle all sorts of interactions off the bat. When reselect is used as a magic bullet, without an understanding of what is happening under the hood or how it functions, it can add useless cycles rather than reduce logic.
Keep state small, components simple, and select everything else - A Redux Mantra
Selecting
The most basic selector is made of 3 parts - the selector itself, the input, and the getters.
const getFoo = state => state.foo
const getDoubleFoo = createSelector(
getFoo, foo => foo * 2
)
const state = { foo: 2 }
getDoubleFoo(state) // 4
These parts are the basic building blocks.
The first function, getFoo
, is the getter, and multiple getters can be passed in. A simple, underived getter doesn't need to be crafted with reselect. I have seen forms like this, where state has a reducer of bar, with a property of foo
const getBar = state => state.bar
const getFoo = createSelector(getBar, bar => bar.foo)
Going this granular with selectors, when nothing has been derived, can actually cause more cycles than necessary.
If you are familiar with reselect, you will know that it memoizes, and stores the result, but perhaps not exactly how. It doesn't do a comparison check on the result of the selector, or on the input - it memoizes based on the results of the getters. I would think of the result of a getter as a kind of focus, and the focus, the result of getBar in this case, is what is memoized and checked against. Every time the inputs change, we check the results of all the getter functions, and recompute if any change.
So, it is better in this case to just do const getFoo = state => state.bar.foo
. We get around the equality check every time the state changes, and we would be doing the equivalent anyway, because if any part of the state changed, we would recompute the whole thing, even though we only care about foo.
Handling
What about property error handling and defaults? Perhaps the derivation was to add a default result, or check along the way to make sure objects exist? Enter Lodash, and lodash/fp. Reselect, and also redux, are at the heart functional libraries. Functions are created to be used as inputs, or selectors, and anything in between. All reselect is is a chain of functions that call getters, that could be other selectors. In this way, selectors can be chained together arbitrarily. Lodash has a functional library, lodash/fp, and can be used to simplify these atomic getters a great deal. You can use the same patterns with regular lodash, but still requires arrow function syntax, so is not as clean in my opinion. In this case, we can change const getFoo = state => state.bar.foo
to
import get from 'lodash/fp/get'
const getFoo = get('bar.foo')
If you use this as a getter in a selector, you can pass in any main structure S into the selector, and the getter will get whatever exists at S.bar.foo. If any part doesn't exist, it handles this gracefully, and returns undefined. If you wanted to get a default with this, you can use the pattern
import getOr from 'lodash/fp/getOr'
const getFoo = getOr({}, 'bar.foo')
To get a default value in the getter of {} if bar or foo does not exist. This is something to watch out for, however - it is an undefined check, not a truthy check. So, if foo is null, getFoo will return null, and not {}. If foo exists but is set to undefined, it would return {} as expected.
Most examples show deriving something from redux state, but it's not coupled, and you can also create selectors to derive from any complex structure
const getName = get('name')
const getCapitalizedName = createSelector(
getName, capitalize
)
const user = { name: 'errastas' }
getCapitalizedName(user) // Errastas
This works fine when you have one set of data to select on, but gets more complicated when you are selecting for items in a list.