DEV Community

Cover image for 🥢Selector in Redux
Nawaz Mujawar
Nawaz Mujawar

Posted on

🥢Selector in Redux

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;
Enter fullscreen mode Exit fullscreen mode

Slightly Complex using ID:

selectUserIds = state => state.users.map(user => user.id);
Enter fullscreen mode Exit fullscreen mode

More Complex:

selectUserIdsOfName = (state, name) => state.users.filter(user => user.name === name);
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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]
    )
  }
}
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Well, now your previous mapStateToProps function is broken. It refers to state.shoppingCart which is now held at state.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)
  }
}
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
sudonitin profile image
Nitin Sahu

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?

Collapse
 
markerikson profile image
Mark Erikson

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.

Collapse
 
sudonitin profile image
Nitin Sahu

Wow.. Thanks for the amazing explanation...🔥🔥