Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

useSelector destructuring vs multiple calls

Recently I was been reading react-redux docs https://react-redux.js.org/next/api/hooks And there was a section related to Equality Comparisons and Updates, which says:

Call useSelector() multiple times, with each call returning a single field value.

First approach:

const { open, importId, importProgress } = useSelector((importApp) => importApp.productsImport);

Second approach:

const open = useSelector((importApp) => importApp.productsImport.open);
const importId = useSelector((importApp) => importApp.productsImport.importId );
const importProgress = useSelector((importApp) => importApp.productsImport.importProgress);

So is there any real differences? Or due to destructuring useSelector hook will have troubles with checking refences?

like image 707
Chewiex Avatar asked Nov 27 '19 14:11

Chewiex


People also ask

Can I use useSelector multiple times?

You may call useSelector() multiple times within a single function component. Each call to useSelector() creates an individual subscription to the Redux store.

What is use of useSelector in react?

useSelector is a function that takes the current state as an argument and returns whatever data you want from it and it allows you to store the return values inside a variable within the scope of you functional components instead of passing down as props.


2 Answers

Just to lay the groundwork: upon an action being dispatched, the selector you pass to useSelector() will be called. If the value it returns is different to the value returned last time an action was dispatched, the component will re-render.

Destructing is indeed the wrong approach, but the top answer here is completely irrelevant. The docs refer to a scenario where the selector is creating a new object every time, like you might do in a mapStateToProps() function. That would cause the component to re-render every single time an action is dispatched, regardless of what that action does, because the value returned by the selector is technically a different object in memory even if the actual data hasn't changed. In that case, you need to worry about strict equality and shallow equality comparisons. However, your selector is not creating a new object every time. If a dispatched action doesn't modify importApp.productsImport, it will be the exact same object in memory as before, rendering all of this moot.

Instead, the issue here is that you are selecting an entire slice of state, when you only actually care about a few particular properties of that slice. Consider that importApp.productsImport probably has other properties besides just open, importId, and importProgress. If those other properties change, then your component will needlessly re-render even though it makes no reference to them. The reason for this is simple: the selector returns importApp.productsImport, and that object changed. Redux has no way of knowing that open, importId, and importProgress were the only properties you actually cared about, because you didn't select those properties; you selected the whole object.

Solutions

So, to select multiple properties without needless re-renders, you have two options:

  • Use multiple useSelector() hooks, each selecting a single property in your store.
  • Have a single useSelector() hook and a single selector that combines multiple properties from your store into a single object. You could do this by:
    • Using a memoized selector from reselect.
    • Simply writing a function that creates a new object from specific properties of state and returns it. If you did this, you would then have to worry about strict equality and shallow equality comparisons.

For this purpose, I feel like multiple useSelector() hooks is actually the way to go. The docs make a point of mentioning that

Each call to useSelector() creates an individual subscription to the Redux store.

but whether multiple calls would actually incur a real performance penalty compared to a single call purely for this reason, I think, remains to be seen, and it seems to me that worrying about this is probably over-optimisation unless you have a huge app with hundreds or thousands of subscriptions. If you use a single useSelector() hook, then at that point you're basically just writing a mapStateToProps function, which I feel like defeats a lot of the allure of using the hook to begin with, and especially so if you're writing TypeScript. And if you then want to destructure the result, that makes it even more cumbersome. I also think using multiple hooks is definitely more in the general spirit of the Hooks API.

like image 189
matthewlucock Avatar answered Sep 19 '22 14:09

matthewlucock


When an action is dispatched to the Redux store, useSelector() only does a strict "===" reference comparison. This does not do a shallow check.

So, if you want to retrieve multiple values from the store, you must call useSelector() multiple times, with each call returning a single field value from state. Or use shallowEqual in react-redux:

import { shallowEqual, useSelector } from 'react-redux'

const data = useSelector(state => state.something, shallowEqual)

Refer to https://react-redux.js.org/next/api/hooks#equality-comparisons-and-updates for detailed explanation.

like image 22
Shriprada S Avatar answered Sep 19 '22 14:09

Shriprada S