When I set out to optimize my react-redux app, I’ve gone through plenty of blog posts. Many of them mention using Reselect to improve performance by memoization. I’m a firm believer of learning to reinvent the wheel, but don’t reinvent it and decided to comb through Reselect’s source code to see memoization in practice (instead of only learning it for interview prep😅)

Why Reselect

In a react-redux app, components wrapped in connect function will run mapStateToProps after each dispatch. mapStateToProps calls selectors to compute new props. If the returned props have been updated, the connected components will be re-render. The problem is that selectors need to repeat the computations for every dispatch, which can be costly. Reselect to the rescue, Reselect makes use of an optimization technique called memoization. Wikipedia defines memoization as:

an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.

Reselect avoids re-computations by creating memoized selectors.

How Reselect works

Let’s look at an example to get started.

An example of using Reselect

The code above is taken from the Reselect documentation. createSelector creates a selector by taking an array of input selectors and a transform function as arguments. The selector uses input selectors to retrieve data from the redux store. It then calls transform function with the returned values of the input-selectors as arguments and outputs the result. Here’s where memoization comes into the picture:

If the values of the input-selectors are the same as the previous call to the selector, it will return the previously computed value instead of calling the transform function.

Memoization in Reselect

Reselect’s source code has only 108 lines so it’s not too difficult to understand.

Memoizing one function

Default memoization in Reselect

The code above is an implementation of memoizing a function. It’s taken from the Reselect library. Upon receiving the same set of arguments, a memoized function will return the previously computed value without re-computation.

In line 6, areArgumentsShallowEqual checks whether the same arguments occur again. If so, the function simply returns the cached result. If not, the function will re-compute and cache the updated value.

The code makes use of an important Javascript concept to achieve memoization — closure.

Closure

The key to memorization is storing the last inputs and output. This is done by closure. If you‘re not familiar with closure, I recommend reading this article:

If all you need is a quick refresher, here’s the story. A closure is the combination of a function and its surrounding lexical scopes. A closure is created whenever a function is created. If there is a function enclosed by another, the outer function will return an instance of the inner function. The returned function will be given access to all the variables of the outer function. The closure will keep references to the outer function’s variables. This makes closures particularly useful because we can use them to preserve data after a function call.

In the case of Reselect, defaultMemoize is a higher-order function that has two variables set up: lastArgs and lastResult . The variables serve as caches with a limit of one. A closure is created when defaultMemoize returns an instance of the inner function. Through closure, the inner function keeps a reference to the scope of defaultMemoize so that it can make use of lastArgs and lastResult for shallow comparison.

Memoization in Reselect

Recall that createSelector takes in an array of input selectors and a transform function, selectors compare the values return from input selectors and determine whether to update the derived value with the transform function.

createSelector — the out-of-box API of Reselect

The code above is the implementation of createSelector . This default API is created by invoking createSelectorCreator with defaultMemoize we examine earlier. createSelectorCreator lets us customize and create our own version of createSelector . For example, we can write a new memoize function that uses deep equality check instead of a shallow one.

Let’s take a closer look at createSelector , the returned function of createSelectorCreator .

In lines 4 and 5, the arguments are separated into resultFunc and dependencies by popping the stack. resultFunc is the transform function while dependencies are the input selectors. getDependencies checks the types of input selectors, making sure they are functions.

createSelector memoizes two functions internally.

The first memoized function is resultFunc as seen from line 7 to 14. The input arguments of resultFunc are the returned values of input selectors. With this layer of optimization, resultFunc a.k.a the transform function returns the previously computed value if and only if the return values of input selectors have changed.

From line 17 to 28, there is a second memoized function: selector , the function returned from createSelector and re-used throughout our application. The input arguments of selector are state or props. If a selector is called with the same state or props, we don’t want to traverse input selectors and invoke the transform function again. This offers another layer of optimization.

That’s it!

Hope you have a better understanding of memoization, closure, and Reselect.

If you found this article helpful, I’d appreciate if you hold down the clap button 👏 for a bit. Thanks!