Skip to content

fix: re-create disconnected style resource instances in acquireResource#36398

Open
sleitor wants to merge 1 commit intofacebook:mainfrom
sleitor:fix-36373
Open

fix: re-create disconnected style resource instances in acquireResource#36398
sleitor wants to merge 1 commit intofacebook:mainfrom
sleitor:fix-36373

Conversation

@sleitor
Copy link
Copy Markdown
Contributor

@sleitor sleitor commented May 2, 2026

Summary

When a <style href="..." precedence="..."> resource is rendered inside a createPortal container and that container is later removed from the DOM, React retains a stale reference to the now-disconnected <style> element in the hoistable-styles resource cache.

On the next render of the same href (e.g. the component also renders in the main tree), acquireResource skips the creation branch because resource.instance is non-null, and returns the detached element. The <style> is gone from the document and styles are permanently lost for the rest of the session.

Root cause

ReactFiberConfigDOM.js#acquireResource checks if (resource.instance === null) before creating a new element. When the portal container is removed, resource.instance is still set to the element that lived inside it — it is now disconnected (isConnected: false) but the cache never invalidates it.

Fix

Before the resource.instance === null guard, check whether the cached instance is still connected to the document:

if (resource.instance !== null && !resource.instance.isConnected) {
  resource.instance = null;
}

This causes the code to fall through to the creation path, which re-creates the <style> element and inserts it in the correct hoistable root (the document <head>).

How did you test this change?

Added a regression test in ReactDOMFloat-test.js:

  1. Render a <style href="x" precedence="custom"> inside both a portal container and the main tree.
  2. Remove the portal container from the DOM.
  3. Re-render (hide the portal branch) and verify the style is still present in the document <head>.

All existing Style Resource tests still pass (9 total, +1 new).

Fixes #36373

When a <style precedence> resource is rendered inside a createPortal
container and that container is later removed from the DOM, React kept
a stale reference to the now-disconnected <style> element in the
hoistable-styles cache. On subsequent renders (e.g. when the same href
renders in the main tree), acquireResource would skip the creation path
because resource.instance was non-null, and return the detached element.
The style was then permanently lost for the rest of the session.

Fix: before checking resource.instance === null, also reset it to null
if the cached element is no longer connected to any document. This
causes acquireResource to re-create and re-insert the style element in
the correct hoistable root (the document head).

Fixes: facebook#36373
@meta-cla meta-cla Bot added the CLA Signed label May 2, 2026
@react-sizebot
Copy link
Copy Markdown

Comparing: 9635257...980caf0

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.js = 6.84 kB 6.84 kB = 1.88 kB 1.88 kB
oss-stable/react-dom/cjs/react-dom-client.production.js +0.02% 612.85 kB 612.95 kB +0.02% 108.29 kB 108.32 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.84 kB 6.84 kB = 1.88 kB 1.88 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js +0.01% 678.78 kB 678.88 kB +0.02% 119.26 kB 119.28 kB
facebook-www/ReactDOM-prod.classic.js +0.01% 699.20 kB 699.30 kB +0.02% 122.82 kB 122.84 kB
facebook-www/ReactDOM-prod.modern.js +0.01% 689.51 kB 689.62 kB +0.02% 121.21 kB 121.23 kB

Significant size changes

Includes any change greater than 0.2%:

(No significant changes)

Generated by 🚫 dangerJS against 980caf0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: <style precedence> resources permanently lost when portal container is removed from DOM

2 participants