How We Reselect and You Can Too
Introduction
We’ve all seen the article making fun of the state Javascript was at in 2016.
The constant changing landscape of libraries, frameworks, and packages that we have to keep our codebases updated with is exhausting. The funniest thing about the article is how today many of the solutions listed are out of vogue or have been replaced by something newer and shinier. All this change creates a huge amount of overhead to refactor and integrate into your existing application. Here at our company, Reside Real Estate, we take changes to and additions of libraries very seriously. Every time a new library is introduced, we critique it thoroughly and heavily debate before any idea of implementation can be considered. We are excited to say that Reselect is the newest one to make it past this gauntlet and start the integration process!
What Issues Were We Facing?
Before we really address the solution I want to give you a clear understanding for the motivation. Here are some of the pains we were looking to address:
Minimize Prop Pass through
We wanted to minimize the amount of props that were passed through components and instead fully adopt an approach that pulled data only where it was needed. A main reason for this being that the less superfluous props the less chances of unnecessary component updates.
Increase Reusability
We wanted to have a standardized solution that could pull data from the store without repeating ourselves in every component that needed the data — as well as consolidate logic for derived data.
Maximize Performance
We are leveraging the use of Reacts Pure Components. In order to get the full benefit of this, a memoized solution is needed for any derived props.
What is Reselect?
Reselect offers some amazing optimizations and composability that we really needed. At the absolute basics, Reselect is a memoized Redux selector library. It just makes it really easy to pull data for your component and it accomplishes this with two different types of selectors.
Base Selectors
Base selectors are the building blocks and they are going to pull our raw data. We want to make these as primitive as possible. Ideally, there should be no calculation in these selectors as these will be the basis of comparison for our other selectors memoization.
const baseSelector = (state, props) => SELECT
Note: Really push to only use state in these selectors as any reliance on props makes them far less reusable.
If we just wanted to pull some shopItems
out of state it would look like this:
const getShopItems = state => state.shop.items
Composed Selectors
The second type of selectors are Composed Selectors and these are the heart of Reselect.
import { createSelector } from 'reselect';const composedSelector = createSelector(
[baseSelector, otherComposedSelector],
(baseVal, otherComposedVal) => SELECT
)
The Composed Selectors are built through reselects createSelector
function. The first argument is an array of selector functions. These can either be base selectors or other composed selectors. createSelector
gets the values of those selectors and then passes them as variables into a callback that is provided as the second argument. The return value of this callback is going to be the return value of the selector.
For example, if I wanted to get a subTotal
:
import { createSelector } from 'reselect';// Base Selectorconst getShopItems = state => state.shop.items// Composed Selectorconst getSubtotal = createSelector(
[getShopItems],
items => items.reduce((acc, item) => acc + item.value, 0)
)
You can see that getSubtotal
is using the createSelector
function. With the first argument being the getShopItems
which tells Reselect to go use that selector to get the first value. Reselect resolves the getShopItems
selector and then passes that in to the callback at which point it can calculate and return the subTotal.
On top of making it extremely easy to compose selectors, the createSelector
function has a single look back memoization. So every time that function is called, it will take all inputs and will only recalculate if any have changed. This is really great for some performance optimization in the utilization of a single selector across multiple components.
Once you understand the different types of selectors the final piece of the puzzle is how do you use them? That’s the easiest part. Every selector is called by passing state
and props
as arguments and Reselect then handles passing those arguments to all the the other selectors that are needed. This is the reason the arguments of the base selectors are state and props because eventually every composed selector chain is going to end at base selectors.
import { createSelector } from 'reselect';const getShopItems = (state, props) => state.shop.itemsconst getSubtotal = createSelector(
[getShopItems],
items => items.reduce((acc, item) => acc + item.value, 0)
)const mapStateToProps = (state, props) => ({
subTotal: getSubtotal(state, props)
})
That’s it! All you need to know to be dangerous with Reselect.
How Did We Implement It?
Now that you know what Reselect is, how does it actually look integrated into an existing code base?
It started with figuring out where to put these selectors. With that, there are two very important architecture choices we have made for our application that informed our current decision:
- All the files for a component live in a single folder.
- All component folders can contain children components that themselves have the exact same structure as the parents.
Component
| index.js
| Component.js
| Component.scss
| components
| ChildComponent
| index.js
| ChildComponent.js
| ChildComponent.scss
So, this was an extremely easy choice for us as we could simply place a selectors file alongside the other component files.
Component
| index.js
| Component.js
| Component.scss
| Component.selectors.js
| components
| ChildComponent
| index.js
| ChildComponent.js
| ChildComponent.scss
| ChildComponent.selectors.js
After using this structure for a little while we begin to realize a shortcoming with it, managing imports was very difficult. If we were deep in the component tree and need selectors from multiple levels you could get import statements like this:
import { getTransaction } from 'routes/Transaction/Transaction.selectors'
import { getDocuments } from 'routes/Transcations/components/Documents/Documents.selectors'
import { getName } from './DocumentTile.selectors'
This made it cumbersome to track where everything was being defined and tedious to change where the selector lived, as it would break the fragile importing definitions. We decided to make a small change; inside every selector file we would export all the selectors from its parent creating an amazingly easy-to-use importing system for when we wanted to get data.
The selector files would look like:
export * from '../../PARENT.selectors
and that little change allowed us to simplify the import mess above to now be:
import {
getTransaction,
getDocuments,
getName,
} from './DocumentTile.selectors'
This also made the selectors much more robust as they could move anywhere in the parent chain and still maintain functionality for all child components!
Now that we figured out where the selectors were going to live, the fun really began refactoring our mapStateToProps
functions to be clean selector based code.
const mapStateToProps = (state, props) => {
const { shop: { items, taxPercent } } = state;
const subTotal = items.reduce((acc, item) => acc + item.value, 0);
const tax = subtotal * (taxPercent / 100); return {
subTotal,
tax,
total: subTotal + tax
}
}
With the above example, we can see that this is definitely a perfect contender to use Reselect! We see that there a bunch of calculation in the creation of the props that can be unloaded to selectors. We can start with identifying and writing our base selectors inside our selector file. These are going to be anything that you are pulling directly from state.
export * from '../../Parent.selector';export const getItems = state => state.shop.items
export const getTaxPercent = state => state.shop.taxPercent
Now we start using these base selectors to compose the derived data. The subtotal
can come first as it only needs the shopItems
:
export const getSubtotal = createSelector(
[getShopItems],
shopItems => shopItems.reduce((acc, item) => acc + item.value, 0)
)
Then comes the tax
:
export const getTax = createSelector(
[getTaxPercent, getSubTotal],
(getTaxPercent, subTotal) => subtotal * (taxPercent / 100)
)
Finally, we have everything we need to calculate the total
:
export const getTotal = createSelector(
[getSubtotal, getTax],
(subtotal, tax) => subtotal + tax
)
Here is the complete selector file that we have built:
import { createSelector } from 'reselect';export * from '../../Parent.selector';export const getShopItems = state => state.shop.items;
export const getTaxPercent = state => state.shop.taxPercent;export const getSubtotal = createSelector(
[getShopItems],
shopItems => shopItems.reduce((acc, item) => acc + item.value, 0)
)export const getTax = createSelector(
[getTaxPercent, getSubtotal],
(getTaxPercent, subtotal) => subtotal * (taxPercent / 100)
)export const getTotal = createSelector(
[getSubtotal, getTax],
(subtotal, tax) => subtotal + tax
)
You’ll notice that every one of these selectors are very specific to a single calculation. This is important if we really want to leverage the composability and memoization that Reselect offers.
Now that we structured all of our selectors, we can finally import them in our mapStateToProps
:
import { getSubTotal, getTax, getTotal } from './Shop.selectors'const mapStateToProps = (state, props) => {
subTotal: getSubTotal(state, props),
tax: getTax(state, props),
total: getTotal(state, props)
}
As we were writing our selectors we found that it is best to follow a specific naming convention.
itemName => getItemName
This convention adds consistency and predictability because if I need documents
I know the selector is going to be get getDocuments
. Since we are leveraging this with selector files at every level and exporting all parent selectors, this allows our engineers to quickly pull the exact data they are looking for. These Implementation strategies significantly improve development time and experience!
Conclusion
The tldr of implementing reselect:
- Every selector should follow the same naming convention.
itemName => getItemName
2. Every Component has a selector file.
Component.js
Component.scss
Component.selectors.js
index.js
3. Every selector file imports from its parent.
export * from '../../PARENT.selectors';
We hope that this will help inform your implementation process of Reselect. Our team strives to share our knowledge with the community to create a better coding experience for everyone.
Thanks to Stephen Saunders for being the other half of this push to Reselect. And thanks to Jeff Judkins and Scott Prue for reviewing drafts of this post.
Also, if you haven’t heard of Reside, you should check us out. We are a growing team with a great culture and best of all… we are hiring!