Reselect is a popular library that provides a convenient way of getting values from a store in a React-Redux application. What makes it so good is its memoization ability. You can read all this in the documentation. In two words, when you use the createSelector() function, it memiozes an output of every input selector and recalculates the resulting value only if any of the input selectors changes its output. The important thing to note here is that reselect uses reference equality (===) to determine a value change.
As a motivation to use memoization the documentation suggests an increase of performance, because recalculation on every call may be quite expensive. But we will see in this article that using a memoized selector is sometimes the only way to go in a React-Redux application, even if calculations are very cheap and don’t affect performance.
First of all, let’s take a look at how a React-Redux application works. What Redux does in essence, is provide us with a store for our app’s state and with ways to communicate with the store. One of these ways is the connect() function. After calling connect() on a custom component you get a wrapper that passes state from a store as props to your component. This happens by means of mapStateToProps() function which is called on every state change.
After mapStateToProps() yields recalculated props, the new props are shallow compared to the old ones and if they differ, component gets rerendered. Again, reference equality (===) is used to compare the props.
Here we can make a use of an example. Let’s make up an application called List of Goods. The app will be based on react-boilerplate with an immutable state (see Immutable.js).
We define a state for our app:
import {SET_GOODS, SET_SORTED, COUNT} from 'constants/index';import {fromJS} from 'immutable';
const initialState = fromJS({goods: [{name: 'tomatoes',price: 3,},{name: 'potatoes',price: 2,},{name: 'cucumbers',price: 5,},{name: 'salad',price: 1,}],sorted: false,});
export default (state = initialState, action) => {switch (action.type) {case SET_SORTED: {return state.set('sorted', action.sorted);}default: {return initialState;}}}
And a couple of components:
class GoodsList extends React.Component {render () {return (<div><ul>{this.props.goods.map((g, i) =><li key={i}>{`${g.get('name')} - ${g.get('price')}$`}</li>)}</ul></div>)}}
const mapStateToProps = (state) => {return {goods: getGoods(state),};}
const mapDispatchToProps = (dispatch) => bindActionCreators({count,}, dispatch ;
export default connect(mapStateToProps, mapDispatchToProps)(GoodsList);
class Buttons extends React.Component {render () {return (<div style={{display: 'flex'}}><button style={buttonStyle}onClick={() => this.props.setSorted(true)}>Show Sorted </button><button style={buttonStyle}onClick={() => this.props.setSorted(false)}>Show Unsorted </button></div>)}}
const mapStateToProps = (state) => {return {}}
const mapDispatchToProps = (dispatch) => bindActionCreators({setSorted,}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(Buttons);
Also we have a page containing our components:
export default class HomePage extends React.PureComponent {render() {return (<div><GoodsList/><Buttons/></div>);}}
The app only renders the list of goods, either sorted by price (when a user clicks the ‘Show sorted’ button) or unsorted (by default and when a user clicks the ‘Show unsorted’ button). In the state we have goods which is the very list of goods we want to show and sorted which tells us, if the list should be rendered sorted or unsorted.
The only thing we need to do to get it work now is to define a selector. Let’s try an unmemoized selector first. Basically, this is just a function, like this:
export const getGoods = (state) => {const list = state.getIn(['main', 'goods']);const sorted = state.getIn(['main', 'sorted']);
return sorted ? list.sort((a, b) => {const aPrice = a.get('price');const bPrice = b.get('price');if (aPrice < bPrice) { return -1; }if (aPrice > bPrice) { return 1; }if (aPrice === bPrice) { return 0; }}) : list;}
Depending on the value of sorted, this selector either just returns the list of goods from the state or sorts it by price before returning. Let’s take a closer look at what happens here. The Immutable.js documentation says: “sort() always returns a new instance, even if the original was already sorted”. This means, our sorted list of goods will never be equal to the previously sorted list of goods. Reference equality for goods prop of GoodsList component will never hold, meaning the component will be rerendered on every state change, even if this change doesn’t affect the list of goods in any way.
While this is obviously a wrong way to create a selector, it doesn’t look like a big deal so far. The calculations in the selector are very cheap for our list of only four items, there will not be any significant drop of performance. But what if we need to change state in component’s lifecycle? As React documentation suggests, we will do it in componentWillReceiveProps method. Say, we need to count for some reason how many times GoodsList received props. So, we add componentWillReceiveProps to GoodsList component:
componentWillReceiveProps = (nextProps) => {this.props.count();}
Here count is an action dispatched to store.
And the reducer will look like this:
import {SET_GOODS, SET_SORTED, COUNT} from 'constants/index';import {fromJS} from 'immutable';
const initialState = fromJS({goods: [...],sorted: false,count: 0,});
export default (state = initialState, action) => {switch (action.type) {case SET_SORTED: {return state.set('sorted', action.sorted);}case COUNT: {return state.set('count', state.get('count') + 1);}default: {return initialState;}}}
The following sequence of actions is fired when a user clicks ‘Show sorted’:
This case with counting received props might look a bit artificial, but this may happen in real life. For example, when you set initial values for a redux-form, an action is dispatched to store. Or if you need to store route params in the state, you will set them as an object which will cause the state to change and you are likely to do that in componentWillReceiveProps(), because a route may change without unmounting a component. Both these cases effectively behave as if you count received props in componentWillReceiveProps().
We can easily fix this with Reselect. This selector will work fine in our case:
import {createSelector} from 'reselect';
const getList = (state) => state.getIn(['main', 'goods']);const getSorted = (state) => state.getIn(['main', 'sorted']);
export const getGoods = createSelector(getList,getSorted,(list, sorted) => {return sorted ? list.sort((a, b) => {const aPrice = a.get('price');const bPrice = b.get('price');if (aPrice < bPrice) { return -1; }if (aPrice > bPrice) { return 1; }if (aPrice === bPrice) { return 0; }}) : list;})
Here the transform function will not be called until getList or getSorted change their values which they don’t on COUNT action. Instead, getGoods selector just returns the previously calculated value which is obviously equal to the list already passed to GoodsList component. React doesn’t try to rerender the component for the second time, the cycle breaks.
There is one peril, however: it is quite easy to accidentally make a Reselect selector unmemoized.
For example, you might want to use a JavaScript array in your component instead of the immutable list for some reason. But this selector will again cause an endless cycle to start on ‘Show sorted’ click:
import {createSelector} from 'reselect';
const getList = (state) => state.getIn(['main', 'goods']).toJS();const getSorted = (state) => state.getIn(['main', 'sorted']);
export const getGoods = createSelector(getList,getSorted,(list, sorted) => {return sorted ? list.sort((a, b) => a.price - b.price) : list;})
Although getGoods is a memoized selector here, it gets a different input from getList every time. In general, selectors that get values from the state shouldn’t do anything else, because they are not memoized.
Another possibility for a mistake is curried selectors. Sometimes you want to create selectors like this just in case if you need to pass arguments to a selector in future:
export const getGoods = () => createSelector(...)
And it is tempting to write mapStateToProps this way then:
const mapStateToProps = (state) => {return {goods: getGoods()(state),};}
But here we create a new instance of the selector every time mapStateToProps is called. Basically we just throw away the memoization ability of our selector, because every new instance calculates its value again and this value is not equal to the value calculated by another instance.
These kinds of a bug are quite annoying because often you don’t notice when you create a bug and stumble across it many commits later. Thankfully, git bisect can help you find where it went wrong.
To sum up, I will designate some tips I derived for myself while using Reselect library.
Don’t use selectors like state => state
or state => state.get(‘main’)
as input selectors. If you access big parts of the state, these parts are very likely to change on every action. If you really need to, you will probably have to memoize the transform function yourself. This may be done by using memoize() from Lodash or Ramda or something else.
Also, you can find the example I used here on GitHub if you want to play around with the selectors and simulate some other cases.
Missing part of Redux Saga Experience_Redux saga is a middleware between an application and redux store, that is handled by redux actions. This means, it can…_hackernoon.com
Using Normalizr to organize data in stores — practical guide_After applying few simple manipulations to the result of Normalizr’s work we get data that we can keep in store_hackernoon.com
Using Normalizr to organize data in store. Part 2_The second part of the article about how to use Normalizr to organize data in stores._hackernoon.com
How to Stop Using Callbacks and Start Living_Javascript has two major ways of dealing with asynchronous tasks — callbacks and Promises. In general Promises are…_hackernoon.com
Written by Ilya Bohaslauchyk