Simple "selector" library for Redux inspired by getters in NuclearJS, subscriptions in re-frame and this proposal from speedskater.
- Selectors can compute derived data, allowing Redux to store the minimal possible state.
- Selectors are efficient. A selector is not recomputed unless one of its arguments change.
- Selectors are composable. They can be used as input to other selectors.
npm install reselect
selectors/ShopSelectors.js
/*
The data in the Redux store has the following shape:
store: {
shop: {
items: [
{
name: 'Item 1',
value: 100
},
{
name: 'Item 2',
value: 200
},
{
name: 'Item 3',
value: 300
}
],
taxPercent: 20
}
}
*/
import { createSelector } from 'reselect';
/*
* Definition of simple selectors.
* Simple selectors should be used to abstract away the structure
* of the store in cases where no calculations are needed
* and memoization wouldn't provide any benefits.
*/
const shopItemsSelector = state => state.shop.items;
const taxPercentSelector = state => state.shop.taxPercent;
/*
* Definition of combined selectors.
* In the subsequent examples selectors are combined to derive new information.
* To prevent expensive recalculation of these selectors memoization is applied.
* Hence, these selectors are only recomputed whenever their input selectors change.
* In all other cases the precomputed values are returned.
*/
const subtotalSelector = createSelector(
[shopItemsSelector],
items => items.reduce((acc, item) => acc + item.value, 0)
);
const taxSelector = createSelector(
[subtotalSelector, taxPercentSelector],
(subtotal, taxPercent) => subtotal * (taxPercent / 100)
);
export const totalSelector = createSelector(
[subtotalSelector, taxSelector],
(subtotal, tax) => { return {total: subtotal + tax}}
);
You can use a factory function when you need additional arguments for your selectors:
const expensiveItemSelectorFactory = minValue => {
return createSelector(
[shopItemsSelector],
items => items.filter(item => item.value < minValue)
);
}
const subtotalSelector = createSelector(
[expensiveItemSelectorFactory(200)],
items => items.reduce((acc, item) => acc + item.value, 0)
);
import React from 'react';
import { connect } from 'react-redux';
/*
* Import the selector defined in the example above.
* This allows your to separate your components from the structure of your stores.
*/
import { totalSelector } from 'selectors/ShopSelectors';
/*
* Bind the totalSelector on the Total component.
* The keys of the selector result are bound to the corresponding component props.
* In our example there is the 'total' key which is bound to this.props.total
*/
@connect(totalSelector)
class Total extends React.Component {
render() {
return <div>{ this.props.total }</div>
}
}
export default Total;
Takes an array of selectors whose values are computed and passed as arguments to resultFn.
const mySelector = createSelector(
[
state => state.values.value1,
state => state.values.value2
],
(value1, value2) => value1 + value2
);
// it is not necessary to wrap a single input selector in an array
const totalSelector = createSelector(
state => state.shop.items,
items => items.reduce((acc, item) => acc + item.value, 0)
);
Return a selectorCreator that creates selectors with a non-default valueEqualsFn. The valueEqualsFn is used to check if the arguments to a selector have changed. The default valueEqualsFn function is:
function defaultValueEquals(a, b) {
return a === b;
}
// create a "selector creator" that uses Immutable.is instead of ===
const immutableCreateSelector = createSelectorCreator(Immutable.is);
// use the new "selector creator" to create a
// selector (state.values is an Immutable.List)
const mySelector = immutableCreateSelector(
[state => state.values.filter(val => val < 5)],
values => values.reduce((acc, val) => acc + val, 0)
);