Advanced Redux Patterns: Selectors

????In such a case we could use reselect to cache the result like this:The crucial line is the createSelector(…) method call.

This creates a cached selector for us to use.

This means that the result it computes is going to be saved, along with the parameters that were used to compute it.

At any subsequent call it will compare whether any of the state values that we use there has changed.

If they didn’t, it will return the same value that it calculated earlier, without going through the expensive process again.

So, running this code give us the following result:Computing recent tags…[ 'old ones', 'short story', 'insanity', 'novel' ][ 'old ones', 'short story', 'insanity', 'novel' ][ 'old ones', 'short story', 'insanity', 'novel' ]We can see that the calculations has been done only once, the second and third results are served from cache.

That works pretty well in case of our simple (if a little crazy) example.

However, when we face more complicated real-world uses, we’ll probably find reselect’s caching mechanism to be a little bit limiting and we’d want more control over when and how do we cache the results.

In such a case we’d go one step deeper (or one level of abstraction higher) and use re-reselect.

Hand-crafting your caching with re-reselectReselect seems like a great plug-and-play generalised solution for the problem of selector caching.

It’s simple to use and easy to start with (which is wonderful!), but with this simplicity come some usage limitations.

Caching more resultsOne of the problems that you’ll probably hit with reselect pretty soon is connected to the fact that each selector has a cache limit of one.

Instead of keeping a small store of cache results (for argument A the results was X, for argument B it was Y, etc.

) we can only keep track of the last computed argument — result pair.

Let’s look at a simple feature where this might be a problem.

Imaging that in our article writing app we would like to be able to see which tags have been used in a particular year.

Using reselect this gets a bit complicated, because we’re basically limited to only passing one argument (the app state) into our selector (only the first argument is used to check for caching).

That means that if we wanted to cache that we’d need to create multiple selectors, one for each year, like this:const tagsFrom1918 = createSelector(…);const tagsFrom1919 = createSelector(…);const tagsFrom1920 = createSelector(…);As you can see, that seems a bit over the top.

Of course, we could create a selector factory:const makeYearlyTagSelector = year => { return createSelector( // custom selector definition for a particular year );}const tagsFrom1918 = makeYearlyTagSelector(1918);const tagsFrom1919 = makeYearlyTagSelector(1919);const tagsFrom1920 = makeYearlyTagSelector(1920);This is not only a bit verbose, but comes with a strong disadvantage.

Because such selectors would be dynamically created in each component that uses them, they wouldn’t share the cache between themselves (because they’re different selectors that just coincidentally work the same way).

This means that if we want to get the same information in two different components, we’d have to instantiate two selectors and compute it twice, not once.

With re-reselect, you can handle this problem in a better way.

Let’s first take a look at the solution and we’ll break down the differences afterwards:The output of this code is the following (notice that we only compute the values twice and the last function call hits the already existing cache):Computing recent tags with re-reselect…[ 'old ones', 'short story' ]Computing recent tags with re-reselect…[ 'insanity', 'novel' ][ 'old ones', 'short story' ]There are two big differences worth noting here.

Let’s take a look how it looks in a more general form:const someSelector = createCachedSelector( [selectorA, selectorB, functionThatExtractsAnArgument], (valueA, valueB, selectorArgument) => { // Selector magic goes here })((state, someArgument) => { // We compute the cache key here});When we’re using the createCachedSelector function, it not only defines the selector, but also the way we determine the cache key for it.

We have two specify both.

See how it’s broken down into two parts above.

Take a closer look what arguments createCachedSelector takes.

In reselect we had a bunch of state selectors (for narrowing down what parts of state we’re dealing with) and at the end we had a function that takes all the values that came out of those state selectors to compute the result.

In re-reselect, apart from state, we also get arguments from the selector function call itself (like we are passing year for our tag computation), so we also need additional input selectors to extract those.

They will be later passed to the final computation function in the same way as the values from the state selectors.

What’s the benefit again?Let me rehash the biggest advantage that we get.

Using re-reselect we get parametrised cached selectors that we can reuse all over our app, but still getting the advantage of the common cache that they share.

All of this with, arguably, cleaner syntax than a selector factory that we’d have to use otherwise.

Getting more control with custom cache keysWith re-reselect we need to define a cache key to use when creating our cached selector.

It’s no longer computed automatically (as it is when using pure reselect) and we have full control over how it’s created.

Most often, you’d use a string or a number as the cache key (as in the example above).

This would be typically a parameter (or a string being a unique combination of different parameters) for the selector.

However, for more elaborate cases you have also an option to use a Map object.

Of course, there are also various caching strategies (around cache size and which values get expired first) available for you to use and even a possibility to create your own.

It’s all controlled by the (cacheObject option).

Low-level cache management possible with re-reselectSummaryAfter reading this blog post you’ve read how and why to use custom selectors for your Redux store.

We hope it motivated you to give a closer look at how it’s implemented in your applications and make sure you take advantage of caching to memoize any of the expensive transformations that you make ????If that’s a topic that you found interesting, you should probably check out also our blog post on data normalisation in Redux stores.

As you have probably noticed, those two subjects go closely together.

Also, stay tuned and make sure you subscribe to this blog to get our weekly tips for creating better mobile applications faster ????.

. More details

Leave a Reply