-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix useSelector race condition with memoized selector when dispatching in child components useLayoutEffect as well as cDM/cDU #1536
fix useSelector race condition with memoized selector when dispatching in child components useLayoutEffect as well as cDM/cDU #1536
Conversation
…g in child components useLayoutEffect as well as cDM/cDU
Deploy preview for react-redux-docs ready! Built with commit ee80685 |
codesandbox reproduction by @laxels : |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So you're adding another condition for re-running the selector: if the store's state has been updated since the last time the cache refs were updated. I think this works to elegantly solve the problem.
A tiny side effect of this change is that the selector is now invoked an extra time, which I don't think should matter.
@dai-shi There is actually still a tiny edge case bug left here, though it's much harder to trigger: Check out this test case:
The only difference between this and your test case is that the selector switches from Since the dispatch in It's caused by essentially the same timing issue. Another edge case which may also happen due to the same timing issue is that the selector errors in While I haven't figured out how to pass in a fresh
With this^,
|
Nice catch! It's like this, which can be seen as a wrong usage at a glance: let selector = ...;
setTimeout(() => { selector= ...; }, 1000);
const Comp = {
const selected = useSelector(selector);
...;
};
Can you elaborate this please? This sounds like a realistic case, but I'm not certain. |
It also applies to in-render mutations, though. For example, the super common usage of
^ The above will also miss required re-renders. The problem is that
Yet another contrived example, but it's easy to see how this one could happen randomly in the wild:
When the child dispatches in
It then relies on the next render to throw the error for it. However, the
So the error is silently ignored. |
OK, I get it. Thanks for your patience. As I understand, this only happens when child components trigger dispatch in useLayoutEffect instead of useEffect. Class components are troublesome in this case, because there's no useEffect equivalent.
Yeah, similar for this. We could keep the error in a scoped variable in render and check if it's changed in useIsomorphicLayoutEffect (and forceRender if changed.) |
@markerikson Can you take a look at this? I think the spec is also useful to evaluate |
I think it looks good. I don't have any objections. |
@timdorr why haven't this been merge I need it :( |
I'd just like another approval here and then we can merge this in. |
Seems reasonable. |
Thanks! |
@markerikson |
I'm dealing with a number of TS updates to Redux for 5.0 and working on getting Redux DevTools published and synced up on npm. I haven't had time to work on getting a new React Redux release together, but do have it on my list. |
I'll try to look at putting out a new release this weekend. |
Go right ahead. Doesn't have to wait on me! |
Just for another datapoint: we hit this problem in production today and confirmed that this PR fixed the problem. |
…g in child components useLayoutEffect as well as cDM/cDU (reduxjs#1536) Co-authored-by: Tim Dorr <timdorr@users.noreply.github.com>
This is the continuation of #1532.
It can be related with #1508 too.
This only happened if a parent component uses a selector with useCallback, and a child component dispatch an action in useLayouteffect (or componentDidMount / componentDidUpdate).
You can try how it works or fails with the test case by changing one of conditions: Removing useCallback, or changing useLayoutEffect to useEffect, with reverting the useSelector patch.