Fix false hydration mismatch when client renders portal that returned null on server#36321
Fix false hydration mismatch when client renders portal that returned null on server#36321starboyvarun wants to merge 4 commits intofacebook:mainfrom
Conversation
…was null on server Portals render into a separate container that is never server-rendered. Previously, when a component returned null on the server but a portal on the client, React would incorrectly try to hydrate the portal's children against the parent container's server nodes, producing a false 'Hydration failed' error. Fix: save and reset hydration state when entering a portal (prepareToHydrateHostPortal), then restore it when the portal completes (popHydrationStateAfterPortal). This ensures portal children are inserted fresh rather than incorrectly matched against server HTML. Fixes facebook#12615
|
Hi @starboyvarun! Thank you for your pull request and welcome to our community. Action RequiredIn order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you. ProcessIn order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA. Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks! |
prepareToHydrateHostPortal and popHydrationStateAfterPortal were already
exported via `export function`, so they must not also appear in the
trailing `export {}` block.
|
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks! |
Summary
Fixes #12615
When a component returns
nullon the server but renders aReactDOM.createPortal()on the client, React was incorrectly emitting a hydration mismatch error:This is a false positive — the portal's
<div>renders into a separate container, not into the parent<span>. It should not be compared against the parent's server HTML.Root Cause
When
updatePortalComponentcallspushHostContainerto switch to the portal's container, the hydration state variables (isHydrating,nextHydratableInstance,hydrationParentFiber) were not reset. The portal's children then tried to claim hydration instances from the parent container's server nodes, which failed and triggered a mismatch error.Fix
Added two functions to
ReactFiberHydrationContext:prepareToHydrateHostPortal— called inupdatePortalComponent(begin work). Saves the current hydration state onto a stack and resets it, so portal children are inserted fresh rather than matched against server HTML.popHydrationStateAfterPortal— called in theHostPortalcase of complete work. Restores the saved hydration state so the parent resumes hydrating correctly after the portal.This correctly handles nested portals (via the stack) and cleans up on
resetHydrationState(work loop interruption).Test Plan
ReactDOMHydrationDiff-test.js:ReactServerRenderingHydration,ReactDOMServerPartialHydration, andReactDOMFibersuites pass