Skip to content

useSelector's provided selector function is called twice on mount #1801

@sufian-slack

Description

@sufian-slack

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions