Reputation: 751
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?
Upvotes: 62
Views: 32734
Reputation: 5358
The first option is better:
const { open, importId, importProgress } = useSelector((importApp) => importApp.productsImport);
Because:
useSelector
hook once so there won't be unnecessary rerenders.Upvotes: 0
Reputation: 1079
How to select multiple states ::
const [village, timeZone, date] = useSelector((state) => [
state.active_village,
state.time_zone,
state.date,
], shallowEqual);
Upvotes: 7
Reputation: 51
This answer doesn't have many technical details. Please go through other answers for better understanding. Here, I'm just mentioning use case scenarios only. Hoping you will find it helpful:
Assumption: productsImport has only the fields you have mentioned (open, importId, importProgress)
1. Let say you are going to get all the fields in a single component using useSelector then I would say that you should go with First Approach. As using Second Approach will going to add few extra lines of code with no other benefits. Anyhow your component is going to re-render whenever any of the field's values get updated.
2. Now, Let's say you are using and updating importId in one component(Component1) and the remaining fields in another component(Component2). And, also these components should be rendered on the same window/screen. Then you should use Second Approach.
Component1:
const importId = useSelector((importApp) => importApp.productsImport.importId );
Component2:
const open = useSelector((importApp) => importApp.productsImport.open);
const importProgress = useSelector((importApp) => importApp.productsImport.importProgress);
if you want to use only one selector:
const [open, importProgress] = useSelector((importApp) =>[importApp.productsImport.open, importApp.productsImport.importProgress],
shallowEqual);
'Note: Here do not forget to use shallowEqual. Otherwise, your component will rerender on every useSelector() call.'
If you will use First Approach here, then your both component will reRender everytime you update any of the fields.
Upvotes: 5
Reputation:
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.
So, to select multiple properties without needless re-renders, you have two options:
useSelector()
hooks, each selecting a single property in your store.useSelector()
hook and a single selector that combines multiple properties from your store into a single object. You could do this by:
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.
Upvotes: 74
Reputation: 331
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.
Upvotes: 24