Redux is great. If you’re here, I probably don’t have to convince you of that point, and I’m not going to try — the documentation already does an amazing job of that. Instead, I’d like to talk about a certain aspect of Redux: selectors.
Selectors are one of my favourite things about Redux, but I feel like they’re often misunderstood. There are some aspects of selectors that contribute to this confusion:
- Selectors are completely optional.
- In many cases, selectors are trivial to write.
- The beauty of selectors does not become fully apparent until state shape needs to be refactored.
- Talk of selectors quickly leads to talk of “memoization,” which can be a confusing concept.
- It’s not immediately obvious how to use selectors with other libraries, in particular Immutable.js.
So in this post I’d like to talk about selectors as a whole: what are they? Why do we use them? How do we use them? I’ll assume you’re familiar with the basics of Redux (actions, reducers, stores), so take a chance now to brush up if you need a refresher. I’ll also be using some examples with React and react-redux. (I’m not deeply familiar with how Redux works with other view libraries, but hopefully the concepts translate.)
What are selectors?
A selector is any function that takes in the entire Redux state as its only argument and returns something out of that state:
That’s egregiously abstract, so here’s a simple example of how we would use selectors in the Real World™:
Here we have a really simple Redux app. Our selector (
questionsSelector) lives alongside our reducer, and it simply returns the list of questions stored in our Redux state. Sweet! The reducer details are super secret (a.k.a not important), so I have redacted them for the benefit of all parties.
Now let’s assume we have an awesome React application where we’re actually using our selector:
We have a very simple
QuestionList component, which renders a list of questions (gasp). It gets
connected to our Redux state using the handy-dandy react-redux library, and we use our
questionsSelector to get the questions out of our state and pass them along to the component. This usage is pretty standard for a React application using Redux.
It’s also really boring. Painfully so. Why on Earth are we even bothering to use
questionsSelector? All it does is access a property from the state that we could have just accessed in
mapStateToProps. What a waste of time and code. Right?
In this simple redundancy lies a deep and evocative beauty. Prepare your mind and body.
Why do we even use selectors?
I’ve already mentioned that selectors are completely optional. And they are. We could whip up a Redux application with one billion lines of code and never write or even think about selectors. But we’d have a bad time.
The practice of always using selectors to select anything at all from our Redux state gives us all the benefits of encapsulation. Selectors provide a “public” interface to our Redux state for the rest of our application. Think about it: in a real application, we’d be accessing things from our state in lots of different component files, and we’d be accessing that state in potentially creative and exotic ways. Selectors allow us to hide the shape of our state from our components. So what happens if we want to change our state shape? We just change the implementation of our selectors. What if we want to reuse complex state selection logic? Easy — we keep it all nice and tidy within a selector, and reuse it as much as we please.
For example, say we want to change our state so that
state.questions is renamed to
state.listOfQuestions (contrived, yes, but bear with me). If we’re using a selector, all we have to do is update that selector, and our app will simply just work. But if we’re accessing the state directly in
mapStateToProps, we have to go to every single component file where we’re accessing
state.questions and update it! The very thought makes me shudder.
I have a small React/Redux application I built a while back. At one point, I wasn’t happy with the shape of the state, so I decided to refactor it. Luckily, I had used selectors everywhere, and had a bunch of good integration tests. I completely overhauled the state shape, updated the selectors until the tests passed, and voila! The application still worked. I didn’t have to touch a single component file. It was awesome. I had to take five minutes and just bask in the glory of selectors. Felt good. Still does.
Writing tests for selectors
I’ve alluded to integration tests I wrote for a React/Redux application. This area is another where selectors really shine.
I’m a big fan of not writing unit tests for selectors, reducers, or action creators. I think the most useful tests to write are integration tests that cover all three in one pop. The general idea is this:
- Call an action creator and dispatch the action to the store.
- Extract data from the Redux state using a selector and make assertions on the return value of the selector.
That’s it! We can cover the whole action-reducer-selector contract in one test, and we gain the state encapsulation benefits of selectors, so we don’t have to update any tests if our state shape changes. Here’s a general example of this kind of test:
I first learned about this idea of “duck testing” from this great thread on the Redux GitHub repo, which completely revolutionized how I think about and test Redux applications. A huge thank-you to all the great people in the Redux community.
Selectors and memoization
Let’s modify our example from above. Say we only want our
QuestionList component to render “active” questions. Using the brute power of state encapsulation, all we have to do is modify our selector and bam!, we have our desired behaviour:
Nice. We’ve changed our whole application without modifying any component files. But don’t bask just yet! There’s a problem.
With this innocuous refactor to our selector, we’ve drastically impacted the rendering performance of our React application. Now, instead of returning the list of questions directly out of our state, we’re returning a brand new list every time because we’re calling
.filter(...) on the question list. That means that even if our list of questions hasn’t changed, every component that uses
questionsSelector will re-render with any change to our state— our React components won’t be able to optimize away unnecessary re-renders by doing shallow object comparison. (You can read more about this concept on the React docs for PureComponent.)
This isn’t a huge deal for small apps, but what do small apps quickly become? Big apps. And rendering performance in big apps can be a huge issue. Even in medium-sized apps, to be perfectly honest.
This is where memoization of selectors comes in. In general, memoizing a function means caching its return values, so that when calling the function again with the same arguments, the same return value can be given back without having to recompute it. Going back to our example, we can use a library called reselect to memoize our Redux selector, and then we can talk about how it benefits us:
Now we have two selectors: one to get all the questions out of the state, and one to get the active questions. We’re using reselect’s
createSelector function to create a memoized selector. The first argument to
createSelector is a list of other selectors our selector depends on (i.e. its “dependency selectors”); the second argument is the function we want to memoize, accepting the results of the dependency selectors as arguments. (This interface of listing dependency selectors allows for selector composition.)
This example might look a bit confusing at first, but all it’s really doing is saying “Hey there
questionsSelector, if the value of
state.questions hasn’t changed since I last called you, just give me the exact same value you gave me last time, instead of giving me a brand new object. K thanks bye.” (The code for reselect is quite small, and I’d suggest reading through it to get an idea of how it works.)
When we use our memoized selector in
connect function (and therefore our React components) will get the exact same object reference from the selector every time it is called, as long as
state.questions remains the same. Unnecessary re-renders are then optimized away by the internals of
connect by doing shallow object comparison on the props being passed to our components.
This technique can dramatically improve the performance of a web app.
Some quick notes about reselect (and memoization in general):
- Generally, only use reselect when computing derived data from the Redux state. In our example, the first version of the selector doesn’t need to be memoized because it’s just returning the plain
state.questionsobject every time. The second version benefits from memoization because it means we don’t have to return a new derived value every time. A corollary of this is: only memoize when returning object references from a selector! Primitive types like number, string, boolean — they’re compared by value, so there’s no point in memoizing selectors that return them.
- If there’s a really expensive computation in a selector — for whatever mysterious reason — it might be worth memoizing it even if it doesn’t return an object reference, because it will help save repetitions of that computation. (In general, this case is very rare — if your computation is that expensive, it might not belong on the front-end.)
- Just like selectors themselves, memoizing is completely optional! Write plain selectors first, then memoize as needed.
Using selectors with Immutable.js
I used to hate Immutable.js. Dealing with the awkward APIs annoyed me; I always seemed to be fighting with it, and the pain didn’t seem worth the benefits.
Then I learned I was using it all wrong.
In a React/Redux application, Immutable.js objects should only live within reducers. That’s where the most benefit is derived: Immutable.js magically turns foul immutable update logic into clean and concise code.
But once Immutable.js gets into React components themselves, all hell breaks loose. React components that accept Immutable components as props are less portable and more difficult to refactor.
That’s where selectors come to the rescue. Selectors let us keep Immutable.js objects outside our presentational components! Let’s refactor our favourite selector from above, assuming our Redux state is now using Immutable.js objects:
Alright. It looks pretty much the same. Nice. Except it now returns an Immutable.js object! That’s not good — we want to be able to refactor our selectors without touching our component files. So we naively tack a
.toJS() onto the end of our selector and whoops, we run into the same problem as we talked about above! Calling
.toJS() returns a brand new object every time, so we have to memoize our selector, lest we suffer the wrath of the dreaded vacuous re-render.
And it quickly becomes tiresome: calling
The Redux docs have a whole page on using Immutable.js with Redux, and I learned a lot from going through it. Some of the highlights that I really stand by include:
- Always return Immutable.js objects from selectors. This practice eases selector composition and maintains a consistent selector interface.
- Do NOT call
.toJS()in selectors, in order to avoid unnecessary re-renders.
- Do NOT pass Immutable.js objects to presentational components. This practice keeps components portable and makes them easier to refactor.
- Do NOT call
mapStateToProps, also to avoid unnecessary re-renders. (As it so happens,
mapStateToPropsitself is just special kind of selector, so this point is technically just a reiteration of the earlier ones.)
Looking at these points, we notice a problem — a pretty big problem: There’s no way left to get data from our Redux state into our presentational components!
questionsSelector returns an Immutable.js List:
What’s the idea behind this
withImmutablePropsToJS higher-order component? Basically, it detects any Immutable.js props that are passed in, and automatically calls
.toJS() on them, passing any other props through unchanged. This way,
connect still gets the same object reference from
questionsSelector, so it can optimize away unnecessary re-renders, and we still manage to keep Immutable.js objects outside our
Fortunately, the Redux docs give an example of how to implement this higher-order component. We’ve been using it extensively at Top Hat, and it’s really helped us consolidate our usage of selectors and Immutable.js across the organization. The higher-order component itself started cropping up across a bunch of our different front-end projects, so we’ve released an NPM package containing it. Check it out if you’ve got a second, and feel free to use it in your own React/Redux apps: https://github.com/tophat/with-immutable-props-to-js.
Boom! We’ve achieved all of our goals — no need to call
.toJS() in all of our selectors, no Immutable.js objects in our presentational components, no superfluous memoization, no unnecessary re-renders — the whole shebang!
And that’s the last piece of the puzzle of selectors! At least for now. Let’s take a moment to bask in the glory of it all. We’ve earned it.
And so we forge on…
Ah selectors! Beauty, grace, and poise incarnate. I hope you had a few “aha!”s and insights into their usage with Redux, and have a better understanding of memoization and how to work with Immutable.js.
I eagerly await your tales of ecstatic encapsulation.