A “selector” is simply a function that accepts Redux state as an argument and returns data that is derived from that state.
A selector is a small function you write that can take the entire Redux state, and pick out a value from it.
You know how mapStateToProps
works? How it takes the entire state and picks out values? Selectors basically do that. And, bonus, they improve performance too, by caching the values until state changes. Well – they can improve performance.
Why should you use a selector?
It is a best practice to keep your Redux store state minimal and derive data from the state as needed. Selectors help with that. They can compute derived data, allowing Redux to store the minimal possible state. Selectors are also very efficient. A selector is not recomputed unless one of its arguments changes.
Some examples of Selectors:
Basic :
selectUsers = state => state.users;
Slightly Complex using ID:
selectUserIds = state => state.users.map(user => user.id);
More Complex:
selectUserIdsOfName = (state, name) => state.users.filter(user => user.name === name);
An Example
Redux gives you a store where you can put state. In a larger app, that state is usually an object, where each key of the object is managed by a separate reducer.
{
currentUser: {
token,
userId,
username
},
shoppingCart: {
itemIds,
loading,
error
},
products: {
itemsById,
loading,
error
}
}
First, without a selector
- When it comes time to get data out of the Redux state and into your React components, you’ll write a
mapStateToProps
function that takes the entire state and cherry-picks the parts you need. - Let’s say you want to show the items in the shopping cart. To do that, you need the items. Buuut the shoppingCart doesn’t have items. It only has item IDs. You have to take each ID and look it up in the products.items array. Here’s how you might do that:
function mapStateToProps(state) {
return {
items: state.shoppingCart.itemIds.map(id =>
state.products.itemsById[id]
)
}
}
Changing the state shape breaks mapStateToProps
Now – what happens if you decide “You know… shoppingCart
should really be a property of the currentUser
instead of a standalone thing.” And then they reorganize the state to look like this:
currentUser: {
token,
userId,
username,
shoppingCart: {
itemIds,
loading,
error
},
},
products: {
itemsById,
loading,
error
}
}
- Well, now your previous
mapStateToProps
function is broken. It refers tostate.shoppingCart
which is now held atstate.currentUser.shoppingCart
. - If you had a bunch of places in your app that referred to
state.shoppingCart
, it’ll be a pain to update all of them. Fear or avoidance of that annoying update process might even prevent you from reorganizing the state when you know you should.
If only we had a way to centralize the knowledge of the shape of the state… some kind of function we could call that knew how to find the data we wanted…
Well, that’s exactly what a selector is for :)
Refactor: Write a simple selector
Let’s rewrite the broken mapStateToProps
and pull out the state access into a selector.
// put this in some global-ish place,
// like selectors.js,
// and import it when you need to access this bit of state
function selectShoppingCartItems(state) {
return state.currentUser.shoppingCart.itemIds.map(id =>
state.products.itemsById[id]
);
}
function mapStateToProps(state) {
return {
items: selectShoppingCartItems(state)
}
}
Next time the state shape changes, you can update that one selector and you’re done.
Memoization
- The use of selectors in your application can also provide performance optimizations. Let’s say you have a component that needs to run an intensive sorting operation on the store’s state in order to get the data it needs. If you were to perform the operation in your mapStateToProps() function, without the use of a selector, the operation would run every time a dispatched action caused the state to update!
- It would be great if we could only run the expensive sorting operation only when the data we are running the operation on changes. This is where the concept of memoization comes to the rescue.
- Memoization is a form of caching. It involves tracking inputs to a function, and storing the inputs and the results for later reference. If a function is called with the same inputs as before, the function can skip doing the actual work, and return the same result it generated the last time it received those input values.
Conclusion
A selector is a function that accepts Redux state as an argument and returns data that is derived from that state. Selectors can provide performance optimizations to your application and can also help you encapsulate your global state tree.
Top comments (3)
Wow great read.. never knew that there was difference between useSelector and mapStateToProps.
Just a quick question though, as per I understood from the article useSelector implements memoization to improve the performance, right?
No.
useSelector
just calls whatever your provided selector function is, and compares the current result reference vs the last result reference. If they're not===
equal, it forces your component to re-render. Also,useSelector
calls your selector function after every dispatched action to see if the data that this component needs has changed or not.That means that if you have a selector that unconditionally returns a new reference, like
state => state.todos.map(t => t.text)
, then it returns a new array reference for every action, and that causes the component to always re-render - even if it didn't need to!.Memoization is implemented by you, when you create the selector function. This is normally done using the
createSelector
API from the Reselect library, which generates memoized selectors.See my post Using Reselect Selectors for Encapsulation and Performance for details.
There's also a new library called github.com/dai-shi/proxy-memoize , which generates seletors that use ES6 Proxies to track what data is being accessed and whether those values have changed. It's not used widely yet, but it works better than Reselect in some cases, and I'm actively looking at it as an option that we can recommend to Redux users.
Wow.. Thanks for the amazing explanation...🔥🔥