Skip to content

Instantly share code, notes, and snippets.

@9oelM

9oelM/blog.md Secret

Created September 13, 2020 15:11
Show Gist options
  • Save 9oelM/ee356e37d2aa92ed55645ca31c69f3ba to your computer and use it in GitHub Desktop.
Save 9oelM/ee356e37d2aa92ed55645ca31c69f3ba to your computer and use it in GitHub Desktop.
title date category
How to make useSelector not a disaster
2020-09-13T09:00:00.009Z
javascript
react

Disclaimer: We will focus on useSelector itself in this article, rather than third-party libraries like reselect, because it's out of scope of the article.

How was it like before?

Now, before talking about useSelector, we need to know some background. Let's go back to pre-function component era, where all we needed to care about were the props. Not so much you needed to do. Choices were PureComponent and shouldComponentUpdate. Nothing else. Like this:

https://gist.github.com/07d40c5d6aa0c9a10757afe0670dd453

Right. So if you make such component, this component is only going to update when shouldShowRed is changed. Otherwise, it is going to stay still even if its parent renders for some reason. It goes the same for shouldComponentUpdate; It just gives you additional tooling to specify your own method of telling the component when to update.

Using it with redux?

Again, using the component with redux was pretty straightforward too. Just create a container and pass states and dispatches (I didn't write any mapDispatchToProps for the sake of simplicity) mapped as props:

https://gist.github.com/6ca97ad910d3b7e7f69895adc8f6720b

Right. Let's assume that we have RootReduxState as we have seen from the code above. And this is just going to work so well. Nothing difficult here. SomeDiv will keep being efficient at its best because PureComponent is working well. But how should we precisely port this example to a function component?

Function component

Well, it's easy. Just use useSelector, Right?

https://gist.github.com/ff02d9e5302c2d0c6bd02875ce7badcf

Perfect. But what if you need more than just shouldShowRed? The moment you start to get more than one thing from useSelector (which is a very normal case), you are going to need additional optimization efforts with appropriate knowledge.

If we were to use shouldShowGreen too, we could choose:

Option 1

https://gist.github.com/0497c16c622dcde9f11171e8fd147af5

Or, we could:

Option 2

https://gist.github.com/fb90dbf934e94bff6c17d6a020a0919a

Alternatively:

Option 3

https://gist.github.com/c76f9ca8041fb2ce133d319cc9fb721e

Or, just a small variant..

Option 4

https://gist.github.com/5b4e17388fd4bdf624badf04c2980fcb

useSelector

Which method have you been using? Whichever one you are using, you should be able to give reasons! Now, before having a look at each option, let's go back to useSelector and see what it does.

So let me bring the most important thing to the table immediately: useSelector forces re-render of your component. It's not a prop, but it can still make your component re-render. Really? Yes. Let's look at the offical documentation:

When an action is dispatched, useSelector() will do a reference comparison of the previous selector result value and the current result value. If they are different, the component will be forced to re-render. If they are the same, the component will not re-render.

So how do you judge if they are different? By running strict equality (===) comparision. (Click here if you are feeling like looking at the official repo's source code). In the source code, useSelector by default uses a function called refEquality, and all it does is simply const refEquality = (a, b) => a === b.

This means that if you are returning an object from useSelector for whatever reason, useSelector will cause a re-render every time an action is dispatched from the store, simply because objects of the same structure (same keys and values) do not strictly equal each other in javascript. For example, { a: 1 } === { a: 1 } is false. Official doc says the same:

returning a new object every time will always force a re-render by default.

Do they really force it? Yes. From the source code:

https://gist.github.com/b1feeada2bdd8a9aab3da79f1bf8f937

So... now, with this in our mind, let's go back to the options we saw previously.

Which one to pick

Option 1

https://gist.github.com/d8bad6d80265c047255dadcda29171c9

Catches:

  1. You are just writing a pair of shouldShowRed and shouldShowGreen for three times just to take the desired state out.
  2. This will cause a re-render forcefully every time an action is dispatched, because you are returning a new object from your selector.

Verdict: not good enough.

Option 2

https://gist.github.com/d089b2cab64e9414b1e2a2533cfa47fe

Catches: you are returning the entire state from your selector, and destructuring it outside of the selector. This will too cause a re-render. What's the point of having the selector callback if you intend to receive the entire state? This is a bad practice. It's just tantamount to passing the entire state in mapStateToProps. You don't do that there. So, why here?

Verdict: not good enough.

Option 3

https://gist.github.com/71cb411b06a3a10170d633da2cbe851f

Catches:

  1. you are calling useSelector twice, and this does not matter. According to the official doc:

    Because of the React update batching behavior used in React Redux v7, a dispatched action that causes multiple useSelector()s in the same component to return new values should only result in a single re-render.

  2. strict equality is functioning as properly for each selected state because you are returning a primitive from each selector.

Verdict: usable.

Option 4:

https://gist.github.com/d271de9483f63b9413de0d842bc607cd

Catches: It's just the same as option 1 or 2. It will cause a re-render too.

Verdict: Not good enough.

Improving the bad options

Thankfully, redux gives us a chance to insert our own equality functions. The concept is the same shouldComponentUpdate or the equality callback in React.memo. We could do this:

https://gist.github.com/af0f1a2984e90754c2b5a903336794b3

or,

https://gist.github.com/b1be7bc0a042e8577df35d31de14ea8c

Something like that. However you should really note that using deepEqual cannot ever be fast if you are trying to equal a large object (Dan Abramov said it, too!):

dan

Note also that you only want to run deepEqual on what you need. In the code snippet above, you are also comparing shouldShowBlue which is a part of RootReduxState['conditions']. But you don't need that anyways, but you are still comparing it. Make sure you select and compare what you only need.

But why would you try optimizing it in the first place?

Well, at first, it's totally okay. Your app has ~100 components only, your redux state is quite shallow, and it does not take a long time to render whatever's being rendered.

The problem comes at two points:

  1. once you start to scale your application. If you succeeed in making a popular application, you are going to support more features, and thus, need more components. Your components will be numerous, leading to the point where re-render of expensive components will be causing a sluggish interaction.
  2. once you start to care about users using low-end devices. You want to support users with low-end spec computers. You want to support mobile devies with inherently less performance than most computers. Just get a 6x slowdown on your CPU from Chrome's performance tool and try to see how long it takes for your components to react.

Conclusion

So far we looked at possible problems with using useSelector and how to solve them:

  1. If you are returning an object from your selector callback, it's going to force re-render by default.
  2. To prevent re-render, either let your selector return a primitive type, or use a custom equality function.

That's it. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment