DEV Community

Cover image for How to memoize correctly using Redux reselect for O(1) complexity?
Tilak Madichetti
Tilak Madichetti

Posted on • Updated on

How to memoize correctly using Redux reselect for O(1) complexity?

So we are used to writing redux selectors like this:

// state is the universal redux state
// shop is one of the combined reducers
// O(1)
const selectShop = state => state.shop;

// collections is an object - O(1)
export const selectCollections = createSelector(
  [selectShop],
  shop => shop.collections
);

//return all collections as an array - O(N)
export const selectCollectionsForPreview = createSelector(
  [selectCollections],
  collections => Object.keys(collections).map(key => collections[key])
);

Enter fullscreen mode Exit fullscreen mode

With this set up, for a component if you wish to retrieve the collections as an array you could do this:


const mapStateToProps = createStructuredSelector({
  collections: selectCollectionsForPreview
})

export default connect(mapStateToProps)(MyComponent);

Enter fullscreen mode Exit fullscreen mode

When the component is rendered for the first time, the O(N) operation will take place but from the second time (of course assuming the state hasn't mutated) it will just returned the collections array from the cache which makes it an O(1) operation.

Now think of a selector that should return collections as an array but filtered based upon a url parameter

Then you would have to create a function that generates a createSelector function that would look like this
which is an O(N) operation

export const selectFilteredCollections = urlParam =>
  createSelector(
    [selectCollections],   // this returns the array as explained above and is memoized
    collections => collections.filter(coll => coll.type === urlParam)
  );

Enter fullscreen mode Exit fullscreen mode

Now to use it you would do


const mapStateToProps = (state, ownProps) => ({
  collections: selectFilteredCollections(ownProps.match.url)
});

export default connect(mapStateToProps)(MyComponent);

Enter fullscreen mode Exit fullscreen mode

The problem here is you are creating a new function that returns a createSelector function based on the url parameter each time the component renders.

So even if the url parameter remains the same for the second time of the call you are re creating a selector. So memoization didn't take place correctly.

In this situation, you will have to install loadash library

If using yarn:

yarn add lodash.memoize

If using npm:

npm install lodash.memoize

And to use it, we import our newly installed memoize helper function like so

import memoize from 'lodash.memoize';
Enter fullscreen mode Exit fullscreen mode

And just wrap our selectFilteredCollections function with memoize like so:

export const selectFilteredCollections = memoize((urlParam) =>
  createSelector(
    [selectCollections],   // this returns the array as explained above and is memoized
    collections => collections.filter(coll => coll.type === urlParam)
  ));
Enter fullscreen mode Exit fullscreen mode

Memoize does the same idea of memoization as reselect does for our selectors, except this time we're memoizing the return of our function which returns our selector:

(urlParam) =>
  createSelector(
    [selectCollections],
    collections => collections.filter(coll => coll.type === urlParam)
 )
Enter fullscreen mode Exit fullscreen mode

By wrapping this function is memoize, we're saying that whenever this function gets called and receives urlParam, I want to memoize the return of this function (in this case we return a selector). If this function gets called again with the same urlParam , don't rerun this function because we'll return the same value as last time, which we've memoized so just return the selector that's been stored.

Hope you had fun reading this !
Its my first post here :)

And oh ! did I forget to mention - you can learn more
at the official website Redux Reselect

Also if you want to dive deep into loadash.memoize , checkout this article that @CameronNokes wrote here on dev.to

Top comments (7)

Collapse
 
tilakmaddy_68 profile image
Tilak Madichetti • Edited

always try for Time complexity of O(1) folks ! Space complexity isn't a big deal with modern browsers anymore

Collapse
 
youroff profile image
Ivan Yurov

Ah, is the reason why open tab with facebook takes 1.5Gb of RAM that it's not a big deal? Gooootcha... :)

Collapse
 
tilakmaddy_68 profile image
Tilak Madichetti

1.5GB ? 😢

Thread Thread
 
youroff profile image
Ivan Yurov • Edited

Just to illustrate that space complexity still matters, and that time complexity in many cases can be easily sacrificed too. dev-to-uploads.s3.amazonaws.com/i/...

Thread Thread
 
tilakmaddy_68 profile image
Tilak Madichetti

alright now I agree ☝️

Thread Thread
 
tilakmaddy_68 profile image
Tilak Madichetti

;P did you actually upload the image to s3 bucket just to reply to the comment ?

Thread Thread
 
youroff profile image
Ivan Yurov

No, there's a button below comment form, but it works weird... apparently it just uploads an image and gives you the link, so I just copied that link.