React, Reselect and Redux

React is awesome. Redux is awesome, Reselect is awesome. It can be tricky to get all three to play nice together. I will try and explain, in detail, how they work together.

npm install --save reselect

If you have no knowledge of React, Redux and Redux-Connect, stop, go learn those things and comeback later.

Reselect creates memoized selectors, composed of selectors

Yep..that is why this can be tricky. Selectors of selectors. Selector-inception.

Skip to the bottom to see how short the code can get.

Step one: create selectors…you’ve done this in your connect method, only now we move this into it’s own file.

selectors.js

const getBar = (state) => state.foo.bar

I just wrote a selector, this has nothing to do with Reselect. It’s what you’ve probably been doing in your connect method, only now it is a separate function. It gets the state object and returns whatever state.foo.bar is.

Now for Reselect

Reselect is a memoized selector function, composed of selectors, that returns something you want in your component’s props.

import { createSelector } from 'reselect'
// selector
const getBar = (state) => state.foo.bar
// reselect function
export const getBarState = createSelector(
[ getBar ],
(bar) => bar
)

I know that syntax looks funny at first, but here’s what happens. createSelector is a function. First argument, selectors. Second argument is a function called with the selectors as arguments.

So in the above (bar) => bar is a function. The only argument in this function (which I call bar, but could be called anything) is the selector I specified in the first argument of the createSelector function.

Still a bit hard to comprehend…I know. Stick with me for a bit and you shall see. It all comes together when you put it into mapStateToProps

Component File

import React from 'react'
import { connect } from 'react-redux'
import { getBarState } from '../selectors'
const mapStateToProps = (state) => {
return {
bar: getBarState(state)
}
}
class Thing extends React.Component {
...
}
export default connect(mapStateToProps)(Thing)

You see what we did there? Standard set-up, only we have our selector function getBarState(state) rather than state.foo.bar

We passed our state into getBarState

getBarState calls getBar with state as the argument.

getBar goes into our state object and returns bar.

We use that return value in our Reselect function.

Reselect handles the memoization.

In Redux, whenever an action is called anywhere in the application, all mounted & connected components call their mapStateToProps function. This is why Reselect is awesome. It will just return the memoized result if nothing has changed.

Advanced Usage

In the real world, you will most likely need the same certain part of your state object in multiple components. You will also want to pass props to your selector. To do this, you need to create a selector function that can be used on multiple instances of the same component at the same time….all while being properly memoized.

In order to properly memoize a selectorFunction for reusability, it can not be written in the same way…and your mapStateToProps must also change if it is to properly memoize.

You need to create a new instance of your selector function every time it is needed for proper memoization on multiple components.

To do this, change your selectorFunction to be an anonymous function, that returns a selectorFunction

import { createSelector } from 'reselect'
// I am a generic selector, I need no special treatment
// I am now passing props so I can find a specific bar
const getBar = (state, props) => {
const id = props.id
const barById = state.foo.bar.find((item,i) => item.id === id)
return barById
}
// This no longer will work if it is needed in multiple places
//export const getBarState = createSelector(
// [ getBar ],
// (bar) => bar
//)
// This is proper if it is needed in multiple instances
export const makeGetBarState = () => createSelector(
[ getBar ],
(bar) => bar
)

Now when I call makeGetBarState I get a new instance of a createSelector function.

To use this in my component I have to change things there as well.

import React from 'react'
import { connect } from 'react-redux'
import { makeGetBarState } from '../selectors'
// remember, mapStateToProps can have the component's
// props as the second argument
// but now we need to modify this method to allow for a new
// instance of our makeGetBarState function, which returns a
// memoized selector
const makeMapStateToProps = () => {
const getBarState = makeGetBarState()
const mapStateToProps = (state, props) => {
return {
bar: getBarState(state, props)
}
}
return mapStateToProps
}
class Thing extends React.Component {
...
}
export default connect(makeMapStateToProps)(Thing)

You need to change the standard mapStateToProps to be an anonymous function, that returns a mapStateToProps function…hence makeMapStateToProps

In the makeMapStateToProps, I create a new instance of my selector function with the const getBarState = makeGetBarState()

Now getBarState is a selector function, independent of any other selector function, and it will properly memoize across multiple mounted & connected components.

Clean it up

We all like short and sweet…right? Here is how you can refactor the above to be concise

selector.js

import { createSelector } from 'reselect'
const getBar = (state, props) => state.foo.bar.find( b => b.id === props.id )
export const makeGetBarState = () => createSelector(
getBar,
(bar) => ({ bar })
)

component

import React from 'react'
import { connect } from 'react-redux'
import { makeGetBarState } from '../selectors'
const makeMapStateToProps = () => {
const getBarState = makeGetBarState()
return (state, props) => getBarState(state, props)
}
class Thing extends React.Component {
...
}
export default connect(makeMapStateToProps)(Thing)

Reselect has some cool features. I love createStructuredSelector, check it out!

Like what you read? Give Dan Parker a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.