-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Description
What 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