useSelector's provided selector function is called twice on mount
See original GitHub issueWhat version of React, ReactDOM/React Native, Redux, and React Redux are you using?
- React: 17.0.2
- ReactDOM/React Native: 17.0.2
- Redux: 4.1.1
- React Redux: 7.2.
What is the current behavior?
While refactoring some connect()
-based components to be functional hook-based components using useSelector()
, I noticed some degraded performance.
It looks like on initial mount, selectors passed to useSelector()
are called twice when they only need to be called once.
A test case demonstrating the calls and call counts can be shown here: https://codesandbox.io/s/use-selector-twice-lgovv?file=/src/index.js
Upon loading, the console log shows:
1. useSelector: render
1. useSelector: useSelector
2. useSelector+useCallback: render
2. useSelector+useCallback: useSelector
3. mapStateToProps: mapStateToProps
3. mapStateToProps: render
1. useSelector: useSelector
2. useSelector+useCallback: useSelector
The last two useSelector
calls are surprising, as they occur after their corresponding components have rendered, but themselves do not trigger a call to the functional component.
As an aside, I don’t believe this is an issue, but did notice some undocumented/surprising behavior:
If you pass an inline function to useSelector
, like:
function MyComponent({ myValue }) {
const myResult = useSelector((state) => getMyResult(state, myValue));
// ...
}
getMyResult
will be called twice for each redux subscriber notification, this is due to useSelector
seeing that the selector function passed has differed, and as a result will call the selector function. To avoid this, the selector passed to useSelector
can be memoized:
function MyComponent({ myValue }) {
const getMyResultMemo = useCallback((state) => getMyResult(state, myValue), [myValue]);
const myResult = useSelector(getMyResultMemo);
// ...
}
You can see this difference when interacting with the +1 buttons in the linked demo.
What is the expected behavior?
I would expect mounting a component to only call each selector passed to useSelector
once, similar to how on mount connect()
calls the mapStateToProps
function only once.
In the linked demo, I would have expected the console output on load to read:
1. useSelector: render
1. useSelector: useSelector
2. useSelector+useCallback: render
2. useSelector+useCallback: useSelector
3. mapStateToProps: mapStateToProps
3. mapStateToProps: render
Which browser and OS are affected by this issue?
all
Did this work in previous versions of React Redux?
- Yes
Issue Analytics
- State:
- Created 2 years ago
- Comments:8 (8 by maintainers)
Top GitHub Comments
Yep!
I can always put out new 7.x patch releases - we’re still maintaining the 7.x line, we’re just starting to work towards an 8.0 on
master
. (For that matter, I figured out a way to shave some bytes from ourSubscription
implementation during the TS conversion process that I need to apply to 7.x as well.)Thanks, just added two pull requests, one for the
7.x
line and the other formaster
.And thanks for the feedback on not needing to keep track of the first render, the change is much simpler!