Skip to content

useSelector returns stale value in parent component if child component dispatches action updating that value during render #1599

Closed
@tianenli

Description

@tianenli

What is the current behavior?

Demo: https://codesandbox.io/s/race-condition-x1h1d

In this example, our parent component consumes a value from the store, and a child component on render calls an action to update that value multiple times. We see the child component receives the updated value, while the parent component does not after the first update. The parent component's useSelector instance doesn't invoke the selector function and just returns the cached value.

Wrapping the child component's action dispatch in a useEffect solves the issue.
Defining the selector in-line so that each render passes a different function reference, forcing useSelector to call the selector again also works around the issue.

The real-life use case for this pattern is that we have a redirect caused by rendering a react-router redirect component, and this causes an update to the redux store which is storing the current location thru connected-react-router, and the parent component to this consumes location from the store.

import React, { useCallback, useEffect } from "react";
import { incrementAction } from "./actions";
import { useSelector, useDispatch } from "react-redux";

const countersSelector = state => {
  console.log("RUNNING SELECTOR", state.counters);
  return state.counters;
};

function Child(props) {
  const dispatch = useDispatch();
  const increment = useCallback(() => dispatch(incrementAction()), [dispatch]);

  console.log("Child CALLING USESELECTOR");
  const counters = useSelector(countersSelector);
  console.log(counters);

  // wrapping this in a useEffect gets around the issue
  if (counters.counter < 3) {
    console.log("CHILD IS INCREMENTING");
    increment();
  }

  return <div>Child: {counters.counter}</div>;
}

function App(props) {
  console.log("App CALLING USESELECTOR");
  const counters = useSelector(countersSelector);
  console.log(counters);

  return (
    <div className="App">
      <div>App: {counters.counter}</div>
      <Child />
    </div>
  );
}

export default App;

What is the expected behavior?

We would expect the parent component's useSelector instance to return the same value from the store as the child component's

Which versions of React, ReactDOM/React Native, Redux, and React Redux are you using? Which browser and OS are affected by this issue? Did this work in previous versions of React Redux?

The versions used in the code sandbox are:
react: 16.13.1
react-dom: 16.8.3
react-redux: 7.1.3
redux: 4.0.5

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