Imagine we have a reducer to control a list of items:
function listOfItems(state: Array<Object> = [], action: Object = {}): Array<Object> {
switch(action.type) {
case 'SHOW_ALL_ITEMS':
return action.data.items
default:
return state;
}
}
Where Items
looks like this:
type ItemType = {
id: string,
text: string,
completed: boolean
};
Today we mapStateToProps
for all incomplete items like this:
function mapStateToProps(state) {
return {
incompleteItems: state.listOfItems.filter((item) => {
return !item.completed
});
}
}
There are a couple problems with this approach as the application grows.
- The implementation of
incompleteItems
may change. - Computation logic occurs in
mapStateToProps
- Can't memoize the values of
incompleteItems
After talking with Dan Abramov (founder of Redux) he has been preaching the colocation of functions called selectors
with your reducers.
What is a selector
?
- Selectors can compute derived data, allowing Redux to store the minimal possible state.
- Selectors are composable. They can be used as input to other selectors.
Let's turn our filter into a selector
.
function listOfItems(state: Array<Object> = [], action: Object = {}): Array<Object> {
switch(action.type) {
case 'SHOW_ALL_ITEMS':
return action.data.items
default:
return state;
}
}
function getIncompleteItems(state: Object): Array<Object> {
return state.listOfItems.filter((item) => {
return !item.completed
});
}
And we update our mapStateToProps
function:
function mapStateToProps(state) {
return {
incompleteItems: getIncompleteItems(state)
}
}
Now we can reuse this logic across many components mapping this exact state! We can unit test it as well! More importantly we can now memoize this state with reselect
Now don't go and rewrite all your mapFns
! We'll use this pattern going forward on new reducers and Redux connect
components.
Thanks for putting it together.