Redux Selectors: A Quick Tutorial

By Dave Ceddia Comment

You might’ve run up against this concept in Redux called a “selector.”

In this short tutorial I’ll explain what selectors are, why they’re useful, and when (and when not) to use them.

What’s a Selector?

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. We’ll talk about that later.

If you don’t know how mapStateToProps works, you don’t need selectors yet. I recommend you stop right here and go read my Complete Redux Tutorial for Beginners. Selectors are an advanced thing, an extra layer of abstraction on top of regular Redux. Not necessary to understand until you know the basics of Redux.

An Example State Layout

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. Let’s say your state object looks like this:

{
  currentUser: {
    token,
    userId,
    username
  },
  shoppingCart: {
    itemIds,
    loading,
    error
  },
  products: {
    itemsById,
    loading,
    error
  }
}

In our made-up example, it’s keeping track of the logged-in user, the products in your shop, and the items in the user’s shopping cart.

The data here is normalized, so items in the shopping cart are referenced by ID. When a user adds an item to their cart, the item itself is not copied into the cart – only the item’s ID gets added to the shoppingCart.itemIds array.

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 (or another dev on the team) 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 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)
  }
}

Next time the state shape changes, you can update that one selector and you’re done. Easy peasy.

As far as naming goes, it’s pretty common to prefix your selector functions with select or get. Feel free to follow some other convention in your app, of course.

You Probably Don’t Need Selectors for Everything

You might notice that there’s a bit of overhead here. It’s another function to write and test. Another step to follow when you want to add a new piece of state. And potentially another file to think about.

So, for simple state access, or state access that you know is unlikely to be done in a lot of places, you probably don’t need a selector at all. I personally wouldn’t go “all or nothing” with selectors. Write one where it makes sense, and skip it otherwise. It’s an optional abstraction.

Using reselect for Better Performance

The selector we wrote earlier was just a plain function. It hid the detail of the state shape – great! – but it does nothing for performance. There’s no magical caching going on there.

The name is overloaded, too (yay confusion). It’s called a “selector” because it does select data from state, but most of the time when people talk about “selectors,” they mean a memoized one.

A memoized function will “remember” the last set of arguments it received, and the value it returned. Then, the next time it’s called, it’ll check – are these new arguments the same as last time? If they are, return the old value (without recomputing it). Otherwise, recompute, and remember the new set of arguments + return value. Memoization (not memorization… even though the concept is the same) is basically a fancy word for caching.

To create memoized selectors, you could write your own memoization function… or you can install the reselect library. (there are other selector libraries too; reselect is probably the most popular)

yarn add reselect

Then you can use the createSelector function provided by reselect to create a memoized selector. We’re gonna split up our previous selector into a couple atomic, tiny selectors too. I’ll explain why in a second.

import { createSelector } from 'reselect';

const getProducts = state => state.products.itemsById;
const getCartItemIds = state => state.currentUser.shoppingCart.itemIds;

export const selectShoppingCartItems = createSelector(
  getProducts,
  getCartItemIds,
  (products, itemIds) => itemIds.map(id => products[id])
);

What’s going on here?

We’ve split the function up into tiny fragments. Each fragment is a standalone selector for one piece of data. getProducts knows where to find products; getCartItemIds knows how to find the stuff in the cart.

Then, we combine the fragments with the createSelector function. It takes the fragments (as many as you have) and a transform function (that’s what we’re calling the last argument there).

That transform function receives the results from the fragments, and then it can do whatever it needs to do. Whatever it returns is what gets returned by the “master” selector.

createSelector returns the “master” selector which can take state and optionally props, and it passes (state, props) to each of the fragment selectors.

All of this work gets you a nice benefit: the transform function, which might be expensive/slow to run, will only run if one of the fragments returns a different value than last time (make sure the fragments are fast – like plain property access, not like mapping over an array).

I’ll mention again that if your transform function isn’t expensive (as in, it’s only accessing a property like state.foo.bar, or adding a couple numbers together or whatever), then it probably isn’t worth the hassle of creating a memoized selector for it.

Memoize Once, or Memoize Everything?

The reselect library only remembers one call + return – the very last one. If you want it to remember multiple sets of arguments and their return values, take a look at the re-reselect library. It’s a wrapper around reselect, so it works the same on the outside, but on the inside it can cache more stuff.

That’s Selectors in a Nutshell

That pretty much covers the basics! Hopefully selectors make a little more sense now.

comments powered by Disqus