-
Notifications
You must be signed in to change notification settings - Fork 47.6k
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 uSES hydration in strict mode #26791
Conversation
Previously, we'd call and use getSnapshot on the second render resulting in `Warning: Text content did not match. Server: "Nay!" Client: "Yay!"` and then `Error: Text content does not match server-rendered HTML.`. Fixes facebook#26095. Closes facebook#26113.
Comparing: 16d053d...d17a693 Critical size changesIncludes critical production bundles, as well as any change greater than 2%:
Significant size changesIncludes any change greater than 0.2%: (No significant changes) |
Not sure I understand the test failure. |
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.
🥳
Variant tests produce different results though. |
@sophiebits I'm not sure exactly why, but the feature flag causing the failure is |
It's the same as:
For some reason, when in a transition / concurrent rendering, we render the |
Hm, it's because of this check: react/packages/react-reconciler/src/ReactFiberWorkLoop.js Lines 935 to 941 in 7ac5e9a
|
OK, now I think this might be right. |
@@ -1854,7 +1865,7 @@ function updateSyncExternalStore<T>( | |||
); | |||
} | |||
|
|||
if (!includesBlockingLane(root, renderLanes)) { | |||
if (!isHydrating && !includesBlockingLane(root, renderLanes)) { | |||
pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot); |
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.
I don't know what this is so @acdlite is probably best suited for review. Though this
change makes sense to me since we're also not pushing a consistency check when mounting a sync external store during hydration.
); | ||
didWarnUncachedGetSnapshot = true; | ||
let nextSnapshot; | ||
const isHydrating = getIsHydrating(); |
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.
I think this was a regression when I made it so that the "rerender" dispatcher is used during the strict mode double render. Originally the idea was to keep the getIsHydrating
out of the update path because it's always false during an update.
Right now there is no special "rerender" implementation of useSES, but if we add one we can keep this extra check out of the common path.
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.
I am skeptical that the double implementations are good for perf (we should benchmark it sometime) but I can change it.
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.
You mean because of the code size? Execution wise I don't see why it would be slower
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.
Code size and because the function dispatch call is more dynamic so I imagine it can’t inline a jump.
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.
Also, where do we stop? Should we have a fourth implementation for the rerender-on-initial-mount case instead of combining it with the rerender-on-update one like we do today? Right now updateEffect has a branch for currentHook === null.
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.
Oh I was just thinking the rerender implementation would check currentHook
and call the mount or update ones as appropriate. I agree it's not worth duplicating the entire update implementation.
Like I did here for rerenderOptimisticHook:
react/packages/react-reconciler/src/ReactFiberHooks.js
Lines 2049 to 2052 in df12d7e
if (currentHook !== null) { | |
// This is an update. Process the update queue. | |
return updateOptimistic(passthrough, reducer); | |
} |
The main reason we added the separate phases was to keep the mount path fast, not so much the update one. But subjectively I also prefer it for code organization reasons because the logic does end up being significantly different in a lot of the hooks.
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.
Ultimately though my original comment was only a factoring nit. I'd probably structure this differently if I were doing it myself, but I liked how you had it before more than this way, so you could just revert it back to that and if I really want to I can submit a follow up PR later.
The important thing is the fix itself, and the regression test you added.
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.
Happy to change it to what you just described! That way makes sense to me. Wasn’t sure if you approved of the fix originally.
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.
Hmm I'm actually not sure the best way to reuse the mount code on rerender because we need it to call updateWorkInProgressHook including for the interior mountEffect. Gonna land it with the unified update+rerender code like I had before but would be happy to review a refactor if you have one in mind.
I think this also fixes a bug where updateStoreInstance would not be pushed on initial mount rerender.
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.
updated
); | ||
didWarnUncachedGetSnapshot = true; | ||
let nextSnapshot; | ||
const isHydrating = getIsHydrating(); |
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.
Also, where do we stop? Should we have a fourth implementation for the rerender-on-initial-mount case instead of combining it with the rerender-on-update one like we do today? Right now updateEffect has a branch for currentHook === null.
@@ -3991,7 +4079,11 @@ if (__DEV__) { | |||
): T { | |||
currentHookNameInDev = 'useSyncExternalStore'; | |||
updateHookTypesDev(); | |||
return updateSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); | |||
return rerenderSyncExternalStore( |
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.
(also noticed that these should also maybe wrap in InvalidNestedHooksDispatcher although idk why you would try to use a hook in getSnapshot)
This reverts commit 1920123 and does a little cleanup.
Previously, we'd call and use getSnapshot on the second render resulting in `Warning: Text content did not match. Server: "Nay!" Client: "Yay!"` and then `Error: Text content does not match server-rendered HTML.`. Fixes #26095. Closes #26113. Closes #25650. --------- Co-authored-by: eps1lon <[email protected]> DiffTrain build for [4cd7065](4cd7065)
Previously, we'd call and use getSnapshot on the second render resulting in `Warning: Text content did not match. Server: "Nay!" Client: "Yay!"` and then `Error: Text content does not match server-rendered HTML.`. Fixes facebook#26095. Closes facebook#26113. Closes facebook#25650. --------- Co-authored-by: eps1lon <[email protected]>
Previously, we'd call and use getSnapshot on the second render resulting in `Warning: Text content did not match. Server: "Nay!" Client: "Yay!"` and then `Error: Text content does not match server-rendered HTML.`. Fixes #26095. Closes #26113. Closes #25650. --------- Co-authored-by: eps1lon <[email protected]> DiffTrain build for commit 4cd7065.
Has this been included in a release yet? I'm encountering a similar issue but I'm not sure it's related |
Previously, we'd call and use getSnapshot on the second render resulting in
Warning: Text content did not match. Server: "Nay!" Client: "Yay!"
and thenError: Text content does not match server-rendered HTML.
.Fixes #26095. Closes #26113. Closes #25650.