Fix: Activity should hide portal contents - #35091

Merged
acdlite merged 2 commits into
react:mainfrom
acdlite:activity-portals-fix
Nov 10, 2025
Merged

Fix: Activity should hide portal contents#35091
acdlite merged 2 commits into
react:mainfrom
acdlite:activity-portals-fix

Conversation

@acdlite

@acdliteacdlite commented Nov 10, 2025

Copy link
Copy Markdown
Collaborator

Fixes#35000

This PR updates the behavior of Activity so that when it is hidden, it hides the contents of any portals contained within it.

Previously we had intentionally chosen not to implement this behavior, because it was thought that this concern should be left to the userspace code that manages the portal, e.g. by adding or removing the portal container from the DOM. Depending on the use case for the portal, this is often desirable anyway because the portal container itself is not controlled by React.

However, React does own the contents of the portal, and we can hide those elements regardless of what the user chooses to do with the container. This makes the hiding/unhiding behavior of portals with Activity automatic in the majority of cases, and also benefits from aligning the DOM mutations with the rest of the React's commit phase lifecycle.

The reason we have to special case this at all is because usually we only hide the direct DOM children of the Activity boundary. There's no reason to go deeper than that, because hiding a parent DOM element effectively hides everything inside of it. Portals are the exception, because they don't exist in the normal DOM hierarchy; we can't assume that just because a portal has a parent in the React tree that it will also have that parent in the actual DOM.

So, whenever an Activity boundary is hidden, we must search for and hide any portal that is contained within it, and recursively hide its direct children, too.

To optimize this search, we use a new subtree flag, PortalStatic, that is set only on fiber paths that contain a HostPortal. This lets us skip over any subtree that does not contain a portal.

Matches the pattern we use for the other traversals in the commit phase.
@github-actionsgithub-actionsBot added the React Core Team Opened by a member of the React Core Team label Nov 10, 2025
@acdlite
acdliteforce-pushed the activity-portals-fix branch from b976315 to 17ca31aCompareNovember 10, 2025 06:28
@react-sizebot

react-sizebot commented Nov 10, 2025

Copy link
Copy Markdown

Comparing: c83be7d...b748c98

Critical size changes

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

Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-stable/react-dom/cjs/react-dom.production.js=6.68 kB6.68 kB+0.11%1.83 kB1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js+0.01%608.07 kB608.16 kB=107.69 kB107.64 kB
oss-experimental/react-dom/cjs/react-dom.production.js=6.69 kB6.69 kB+0.11%1.83 kB1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js+0.03%665.99 kB666.18 kB=117.39 kB117.35 kB
facebook-www/ReactDOM-prod.classic.js=693.36 kB693.31 kB=122.02 kB121.98 kB
facebook-www/ReactDOM-prod.modern.js=683.78 kB683.73 kB=120.40 kB120.36 kB

Significant size changes

Includes any change greater than 0.2%:

(No significant changes)

Generated by 🚫 dangerJS against b748c98

@acdlite
acdlite marked this pull request as ready for review November 10, 2025 06:33

@rickhanloniirickhanlonii left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, could we add a test? Also seems risky, is it ok to revert if it breaks the sync or could we add a killswitch for the meta builds?

Comment threadpackages/react-reconciler/src/ReactFiberCommitWork.js
Comment threadpackages/react-reconciler/src/ReactFiberCommitWork.js
Comment threadpackages/react-reconciler/src/ReactFiberCommitWork.js
return;
}
case OffscreenComponent:
case LegacyHiddenComponent: {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we omit LegacyHidden from this? Want to limit the risk of breaking changes and if they need this feature they should convert to Activity.

@acdlite

Copy link
Copy Markdown
CollaboratorAuthor

Yeah I wrote some tests just forgot to stage the file

@acdlite
acdliteforce-pushed the activity-portals-fix branch from 17ca31a to 58f2ab4CompareNovember 10, 2025 15:27
@acdlite

acdlite commented Nov 10, 2025

Copy link
Copy Markdown
CollaboratorAuthor

and yes you can revert if it breaks the Meta sync. Assuming it's due to a bug rather than due to the intentional behavior change. If something is relying on the portal not being hidden then I'd prefer not the revert and we should find some other solution for whatever that code is trying to do. (We could revert with a Meta-only flag in the meantime.)

@acdlite
acdliteforce-pushed the activity-portals-fix branch from 58f2ab4 to f2a23f2CompareNovember 10, 2025 15:34
This PR updates the behavior of Activity so that when it is hidden,
it hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit
phase lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's
no reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and
hide _any_ portal that is contained within it, and recursively hide
its direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
@acdlite
acdliteforce-pushed the activity-portals-fix branch from f2a23f2 to b748c98CompareNovember 10, 2025 15:37
@acdlite
acdlite merged commit 5268492 into react:mainNov 10, 2025
239 of 240 checks passed
github-actionsBot pushed a commit that referenced this pull request Nov 10, 2025
This PR updates the behavior of Activity so that when it is hidden, it
hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit phase
lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's no
reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and hide
_any_ portal that is contained within it, and recursively hide its
direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
DiffTrain build for [5268492](5268492)
github-actionsBot pushed a commit that referenced this pull request Nov 10, 2025
This PR updates the behavior of Activity so that when it is hidden, it
hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit phase
lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's no
reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and hide
_any_ portal that is contained within it, and recursively hide its
direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
DiffTrain build for [5268492](5268492)
manNomi pushed a commit to manNomi/react that referenced this pull request Nov 15, 2025
This PR updates the behavior of Activity so that when it is hidden, it
hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit phase
lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's no
reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and hide
_any_ portal that is contained within it, and recursively hide its
direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
@ziyak97

Copy link
Copy Markdown

Is this already out? Will this fix - vercel/next.js#85502 (reply in thread)

If it’s not out any ETA

@abed-daloopa

Copy link
Copy Markdown

When will this be released?

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

Labels

CLA SignedReact Core TeamOpened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Activity mode=“hidden” does not hide nested portals

5 participants

@acdlite@react-sizebot@ziyak97@abed-daloopa@rickhanlonii
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Fix: Activity should hide portal contents - #35091

Merged
acdlite merged 2 commits into
react:mainfrom
acdlite:activity-portals-fix
Nov 10, 2025
Merged

Fix: Activity should hide portal contents#35091
acdlite merged 2 commits into
react:mainfrom
acdlite:activity-portals-fix

Conversation

@acdlite

@acdliteacdlite commented Nov 10, 2025

Copy link
Copy Markdown
Collaborator

Fixes#35000

This PR updates the behavior of Activity so that when it is hidden, it hides the contents of any portals contained within it.

Previously we had intentionally chosen not to implement this behavior, because it was thought that this concern should be left to the userspace code that manages the portal, e.g. by adding or removing the portal container from the DOM. Depending on the use case for the portal, this is often desirable anyway because the portal container itself is not controlled by React.

However, React does own the contents of the portal, and we can hide those elements regardless of what the user chooses to do with the container. This makes the hiding/unhiding behavior of portals with Activity automatic in the majority of cases, and also benefits from aligning the DOM mutations with the rest of the React's commit phase lifecycle.

The reason we have to special case this at all is because usually we only hide the direct DOM children of the Activity boundary. There's no reason to go deeper than that, because hiding a parent DOM element effectively hides everything inside of it. Portals are the exception, because they don't exist in the normal DOM hierarchy; we can't assume that just because a portal has a parent in the React tree that it will also have that parent in the actual DOM.

So, whenever an Activity boundary is hidden, we must search for and hide any portal that is contained within it, and recursively hide its direct children, too.

To optimize this search, we use a new subtree flag, PortalStatic, that is set only on fiber paths that contain a HostPortal. This lets us skip over any subtree that does not contain a portal.

Matches the pattern we use for the other traversals in the commit phase.
@github-actionsgithub-actionsBot added the React Core Team Opened by a member of the React Core Team label Nov 10, 2025
@acdlite
acdliteforce-pushed the activity-portals-fix branch from b976315 to 17ca31aCompareNovember 10, 2025 06:28
@react-sizebot

react-sizebot commented Nov 10, 2025

Copy link
Copy Markdown

Comparing: c83be7d...b748c98

Critical size changes

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

Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-stable/react-dom/cjs/react-dom.production.js=6.68 kB6.68 kB+0.11%1.83 kB1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js+0.01%608.07 kB608.16 kB=107.69 kB107.64 kB
oss-experimental/react-dom/cjs/react-dom.production.js=6.69 kB6.69 kB+0.11%1.83 kB1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js+0.03%665.99 kB666.18 kB=117.39 kB117.35 kB
facebook-www/ReactDOM-prod.classic.js=693.36 kB693.31 kB=122.02 kB121.98 kB
facebook-www/ReactDOM-prod.modern.js=683.78 kB683.73 kB=120.40 kB120.36 kB

Significant size changes

Includes any change greater than 0.2%:

(No significant changes)

Generated by 🚫 dangerJS against b748c98

@acdlite
acdlite marked this pull request as ready for review November 10, 2025 06:33

@rickhanloniirickhanlonii left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, could we add a test? Also seems risky, is it ok to revert if it breaks the sync or could we add a killswitch for the meta builds?

Comment threadpackages/react-reconciler/src/ReactFiberCommitWork.js
Comment threadpackages/react-reconciler/src/ReactFiberCommitWork.js
Comment threadpackages/react-reconciler/src/ReactFiberCommitWork.js
return;
}
case OffscreenComponent:
case LegacyHiddenComponent: {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we omit LegacyHidden from this? Want to limit the risk of breaking changes and if they need this feature they should convert to Activity.

@acdlite

Copy link
Copy Markdown
CollaboratorAuthor

Yeah I wrote some tests just forgot to stage the file

@acdlite
acdliteforce-pushed the activity-portals-fix branch from 17ca31a to 58f2ab4CompareNovember 10, 2025 15:27
@acdlite

acdlite commented Nov 10, 2025

Copy link
Copy Markdown
CollaboratorAuthor

and yes you can revert if it breaks the Meta sync. Assuming it's due to a bug rather than due to the intentional behavior change. If something is relying on the portal not being hidden then I'd prefer not the revert and we should find some other solution for whatever that code is trying to do. (We could revert with a Meta-only flag in the meantime.)

@acdlite
acdliteforce-pushed the activity-portals-fix branch from 58f2ab4 to f2a23f2CompareNovember 10, 2025 15:34
This PR updates the behavior of Activity so that when it is hidden,
it hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit
phase lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's
no reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and
hide _any_ portal that is contained within it, and recursively hide
its direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
@acdlite
acdliteforce-pushed the activity-portals-fix branch from f2a23f2 to b748c98CompareNovember 10, 2025 15:37
@acdlite
acdlite merged commit 5268492 into react:mainNov 10, 2025
239 of 240 checks passed
github-actionsBot pushed a commit that referenced this pull request Nov 10, 2025
This PR updates the behavior of Activity so that when it is hidden, it
hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit phase
lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's no
reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and hide
_any_ portal that is contained within it, and recursively hide its
direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
DiffTrain build for [5268492](5268492)
github-actionsBot pushed a commit that referenced this pull request Nov 10, 2025
This PR updates the behavior of Activity so that when it is hidden, it
hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit phase
lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's no
reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and hide
_any_ portal that is contained within it, and recursively hide its
direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
DiffTrain build for [5268492](5268492)
manNomi pushed a commit to manNomi/react that referenced this pull request Nov 15, 2025
This PR updates the behavior of Activity so that when it is hidden, it
hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit phase
lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's no
reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and hide
_any_ portal that is contained within it, and recursively hide its
direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
@ziyak97

Copy link
Copy Markdown

Is this already out? Will this fix - vercel/next.js#85502 (reply in thread)

If it’s not out any ETA

@abed-daloopa

Copy link
Copy Markdown

When will this be released?

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

Labels

CLA SignedReact Core TeamOpened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Activity mode=“hidden” does not hide nested portals

5 participants

@acdlite@react-sizebot@ziyak97@abed-daloopa@rickhanlonii
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix: Activity should hide portal contents - #35091

Merged
acdlite merged 2 commits into
react:mainfrom
acdlite:activity-portals-fix
Nov 10, 2025
Merged

Fix: Activity should hide portal contents#35091
acdlite merged 2 commits into
react:mainfrom
acdlite:activity-portals-fix

Conversation

@acdlite

@acdliteacdlite commented Nov 10, 2025

Copy link
Copy Markdown
Collaborator

Fixes#35000

This PR updates the behavior of Activity so that when it is hidden, it hides the contents of any portals contained within it.

Previously we had intentionally chosen not to implement this behavior, because it was thought that this concern should be left to the userspace code that manages the portal, e.g. by adding or removing the portal container from the DOM. Depending on the use case for the portal, this is often desirable anyway because the portal container itself is not controlled by React.

However, React does own the contents of the portal, and we can hide those elements regardless of what the user chooses to do with the container. This makes the hiding/unhiding behavior of portals with Activity automatic in the majority of cases, and also benefits from aligning the DOM mutations with the rest of the React's commit phase lifecycle.

The reason we have to special case this at all is because usually we only hide the direct DOM children of the Activity boundary. There's no reason to go deeper than that, because hiding a parent DOM element effectively hides everything inside of it. Portals are the exception, because they don't exist in the normal DOM hierarchy; we can't assume that just because a portal has a parent in the React tree that it will also have that parent in the actual DOM.

So, whenever an Activity boundary is hidden, we must search for and hide any portal that is contained within it, and recursively hide its direct children, too.

To optimize this search, we use a new subtree flag, PortalStatic, that is set only on fiber paths that contain a HostPortal. This lets us skip over any subtree that does not contain a portal.

Matches the pattern we use for the other traversals in the commit phase.
@github-actionsgithub-actionsBot added the React Core Team Opened by a member of the React Core Team label Nov 10, 2025
@acdlite
acdliteforce-pushed the activity-portals-fix branch from b976315 to 17ca31aCompareNovember 10, 2025 06:28
@react-sizebot

react-sizebot commented Nov 10, 2025

Copy link
Copy Markdown

Comparing: c83be7d...b748c98

Critical size changes

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

Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-stable/react-dom/cjs/react-dom.production.js=6.68 kB6.68 kB+0.11%1.83 kB1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js+0.01%608.07 kB608.16 kB=107.69 kB107.64 kB
oss-experimental/react-dom/cjs/react-dom.production.js=6.69 kB6.69 kB+0.11%1.83 kB1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js+0.03%665.99 kB666.18 kB=117.39 kB117.35 kB
facebook-www/ReactDOM-prod.classic.js=693.36 kB693.31 kB=122.02 kB121.98 kB
facebook-www/ReactDOM-prod.modern.js=683.78 kB683.73 kB=120.40 kB120.36 kB

Significant size changes

Includes any change greater than 0.2%:

(No significant changes)

Generated by 🚫 dangerJS against b748c98

@acdlite
acdlite marked this pull request as ready for review November 10, 2025 06:33

@rickhanloniirickhanlonii left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, could we add a test? Also seems risky, is it ok to revert if it breaks the sync or could we add a killswitch for the meta builds?

Comment threadpackages/react-reconciler/src/ReactFiberCommitWork.js
Comment threadpackages/react-reconciler/src/ReactFiberCommitWork.js
Comment threadpackages/react-reconciler/src/ReactFiberCommitWork.js
return;
}
case OffscreenComponent:
case LegacyHiddenComponent: {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we omit LegacyHidden from this? Want to limit the risk of breaking changes and if they need this feature they should convert to Activity.

@acdlite

Copy link
Copy Markdown
CollaboratorAuthor

Yeah I wrote some tests just forgot to stage the file

@acdlite
acdliteforce-pushed the activity-portals-fix branch from 17ca31a to 58f2ab4CompareNovember 10, 2025 15:27
@acdlite

acdlite commented Nov 10, 2025

Copy link
Copy Markdown
CollaboratorAuthor

and yes you can revert if it breaks the Meta sync. Assuming it's due to a bug rather than due to the intentional behavior change. If something is relying on the portal not being hidden then I'd prefer not the revert and we should find some other solution for whatever that code is trying to do. (We could revert with a Meta-only flag in the meantime.)

@acdlite
acdliteforce-pushed the activity-portals-fix branch from 58f2ab4 to f2a23f2CompareNovember 10, 2025 15:34
This PR updates the behavior of Activity so that when it is hidden,
it hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit
phase lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's
no reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and
hide _any_ portal that is contained within it, and recursively hide
its direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
@acdlite
acdliteforce-pushed the activity-portals-fix branch from f2a23f2 to b748c98CompareNovember 10, 2025 15:37
@acdlite
acdlite merged commit 5268492 into react:mainNov 10, 2025
239 of 240 checks passed
github-actionsBot pushed a commit that referenced this pull request Nov 10, 2025
This PR updates the behavior of Activity so that when it is hidden, it
hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit phase
lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's no
reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and hide
_any_ portal that is contained within it, and recursively hide its
direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
DiffTrain build for [5268492](5268492)
github-actionsBot pushed a commit that referenced this pull request Nov 10, 2025
This PR updates the behavior of Activity so that when it is hidden, it
hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit phase
lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's no
reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and hide
_any_ portal that is contained within it, and recursively hide its
direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
DiffTrain build for [5268492](5268492)
manNomi pushed a commit to manNomi/react that referenced this pull request Nov 15, 2025
This PR updates the behavior of Activity so that when it is hidden, it
hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit phase
lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's no
reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and hide
_any_ portal that is contained within it, and recursively hide its
direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
@ziyak97

Copy link
Copy Markdown

Is this already out? Will this fix - vercel/next.js#85502 (reply in thread)

If it’s not out any ETA

@abed-daloopa

Copy link
Copy Markdown

When will this be released?

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

Labels

CLA SignedReact Core TeamOpened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Activity mode=“hidden” does not hide nested portals

5 participants

@acdlite@react-sizebot@ziyak97@abed-daloopa@rickhanlonii
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix: Activity should hide portal contents - #35091

Merged
acdlite merged 2 commits into
react:mainfrom
acdlite:activity-portals-fix
Nov 10, 2025
Merged

Fix: Activity should hide portal contents#35091
acdlite merged 2 commits into
react:mainfrom
acdlite:activity-portals-fix

Conversation

@acdlite

@acdliteacdlite commented Nov 10, 2025

Copy link
Copy Markdown
Collaborator

Fixes#35000

This PR updates the behavior of Activity so that when it is hidden, it hides the contents of any portals contained within it.

Previously we had intentionally chosen not to implement this behavior, because it was thought that this concern should be left to the userspace code that manages the portal, e.g. by adding or removing the portal container from the DOM. Depending on the use case for the portal, this is often desirable anyway because the portal container itself is not controlled by React.

However, React does own the contents of the portal, and we can hide those elements regardless of what the user chooses to do with the container. This makes the hiding/unhiding behavior of portals with Activity automatic in the majority of cases, and also benefits from aligning the DOM mutations with the rest of the React's commit phase lifecycle.

The reason we have to special case this at all is because usually we only hide the direct DOM children of the Activity boundary. There's no reason to go deeper than that, because hiding a parent DOM element effectively hides everything inside of it. Portals are the exception, because they don't exist in the normal DOM hierarchy; we can't assume that just because a portal has a parent in the React tree that it will also have that parent in the actual DOM.

So, whenever an Activity boundary is hidden, we must search for and hide any portal that is contained within it, and recursively hide its direct children, too.

To optimize this search, we use a new subtree flag, PortalStatic, that is set only on fiber paths that contain a HostPortal. This lets us skip over any subtree that does not contain a portal.

Matches the pattern we use for the other traversals in the commit phase.
@github-actionsgithub-actionsBot added the React Core Team Opened by a member of the React Core Team label Nov 10, 2025
@acdlite
acdliteforce-pushed the activity-portals-fix branch from b976315 to 17ca31aCompareNovember 10, 2025 06:28
@react-sizebot

react-sizebot commented Nov 10, 2025

Copy link
Copy Markdown

Comparing: c83be7d...b748c98

Critical size changes

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

Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-stable/react-dom/cjs/react-dom.production.js=6.68 kB6.68 kB+0.11%1.83 kB1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js+0.01%608.07 kB608.16 kB=107.69 kB107.64 kB
oss-experimental/react-dom/cjs/react-dom.production.js=6.69 kB6.69 kB+0.11%1.83 kB1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js+0.03%665.99 kB666.18 kB=117.39 kB117.35 kB
facebook-www/ReactDOM-prod.classic.js=693.36 kB693.31 kB=122.02 kB121.98 kB
facebook-www/ReactDOM-prod.modern.js=683.78 kB683.73 kB=120.40 kB120.36 kB

Significant size changes

Includes any change greater than 0.2%:

(No significant changes)

Generated by 🚫 dangerJS against b748c98

@acdlite
acdlite marked this pull request as ready for review November 10, 2025 06:33

@rickhanloniirickhanlonii left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, could we add a test? Also seems risky, is it ok to revert if it breaks the sync or could we add a killswitch for the meta builds?

Comment threadpackages/react-reconciler/src/ReactFiberCommitWork.js
Comment threadpackages/react-reconciler/src/ReactFiberCommitWork.js
Comment threadpackages/react-reconciler/src/ReactFiberCommitWork.js
return;
}
case OffscreenComponent:
case LegacyHiddenComponent: {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we omit LegacyHidden from this? Want to limit the risk of breaking changes and if they need this feature they should convert to Activity.

@acdlite

Copy link
Copy Markdown
CollaboratorAuthor

Yeah I wrote some tests just forgot to stage the file

@acdlite
acdliteforce-pushed the activity-portals-fix branch from 17ca31a to 58f2ab4CompareNovember 10, 2025 15:27
@acdlite

acdlite commented Nov 10, 2025

Copy link
Copy Markdown
CollaboratorAuthor

and yes you can revert if it breaks the Meta sync. Assuming it's due to a bug rather than due to the intentional behavior change. If something is relying on the portal not being hidden then I'd prefer not the revert and we should find some other solution for whatever that code is trying to do. (We could revert with a Meta-only flag in the meantime.)

@acdlite
acdliteforce-pushed the activity-portals-fix branch from 58f2ab4 to f2a23f2CompareNovember 10, 2025 15:34
This PR updates the behavior of Activity so that when it is hidden,
it hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit
phase lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's
no reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and
hide _any_ portal that is contained within it, and recursively hide
its direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
@acdlite
acdliteforce-pushed the activity-portals-fix branch from f2a23f2 to b748c98CompareNovember 10, 2025 15:37
@acdlite
acdlite merged commit 5268492 into react:mainNov 10, 2025
239 of 240 checks passed
github-actionsBot pushed a commit that referenced this pull request Nov 10, 2025
This PR updates the behavior of Activity so that when it is hidden, it
hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit phase
lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's no
reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and hide
_any_ portal that is contained within it, and recursively hide its
direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
DiffTrain build for [5268492](5268492)
github-actionsBot pushed a commit that referenced this pull request Nov 10, 2025
This PR updates the behavior of Activity so that when it is hidden, it
hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit phase
lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's no
reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and hide
_any_ portal that is contained within it, and recursively hide its
direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
DiffTrain build for [5268492](5268492)
manNomi pushed a commit to manNomi/react that referenced this pull request Nov 15, 2025
This PR updates the behavior of Activity so that when it is hidden, it
hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit phase
lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's no
reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and hide
_any_ portal that is contained within it, and recursively hide its
direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
@ziyak97

Copy link
Copy Markdown

Is this already out? Will this fix - vercel/next.js#85502 (reply in thread)

If it’s not out any ETA

@abed-daloopa

Copy link
Copy Markdown

When will this be released?

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

Labels

CLA SignedReact Core TeamOpened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Activity mode=“hidden” does not hide nested portals

5 participants

@acdlite@react-sizebot@ziyak97@abed-daloopa@rickhanlonii
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Fix: Activity should hide portal contents - #35091

Merged
acdlite merged 2 commits into
react:mainfrom
acdlite:activity-portals-fix
Nov 10, 2025
Merged

Fix: Activity should hide portal contents#35091
acdlite merged 2 commits into
react:mainfrom
acdlite:activity-portals-fix

Conversation

@acdlite

@acdliteacdlite commented Nov 10, 2025

Copy link
Copy Markdown
Collaborator

Fixes#35000

This PR updates the behavior of Activity so that when it is hidden, it hides the contents of any portals contained within it.

Previously we had intentionally chosen not to implement this behavior, because it was thought that this concern should be left to the userspace code that manages the portal, e.g. by adding or removing the portal container from the DOM. Depending on the use case for the portal, this is often desirable anyway because the portal container itself is not controlled by React.

However, React does own the contents of the portal, and we can hide those elements regardless of what the user chooses to do with the container. This makes the hiding/unhiding behavior of portals with Activity automatic in the majority of cases, and also benefits from aligning the DOM mutations with the rest of the React's commit phase lifecycle.

The reason we have to special case this at all is because usually we only hide the direct DOM children of the Activity boundary. There's no reason to go deeper than that, because hiding a parent DOM element effectively hides everything inside of it. Portals are the exception, because they don't exist in the normal DOM hierarchy; we can't assume that just because a portal has a parent in the React tree that it will also have that parent in the actual DOM.

So, whenever an Activity boundary is hidden, we must search for and hide any portal that is contained within it, and recursively hide its direct children, too.

To optimize this search, we use a new subtree flag, PortalStatic, that is set only on fiber paths that contain a HostPortal. This lets us skip over any subtree that does not contain a portal.

Matches the pattern we use for the other traversals in the commit phase.
@github-actionsgithub-actionsBot added the React Core Team Opened by a member of the React Core Team label Nov 10, 2025
@acdlite
acdliteforce-pushed the activity-portals-fix branch from b976315 to 17ca31aCompareNovember 10, 2025 06:28
@react-sizebot

react-sizebot commented Nov 10, 2025

Copy link
Copy Markdown

Comparing: c83be7d...b748c98

Critical size changes

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

Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-stable/react-dom/cjs/react-dom.production.js=6.68 kB6.68 kB+0.11%1.83 kB1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js+0.01%608.07 kB608.16 kB=107.69 kB107.64 kB
oss-experimental/react-dom/cjs/react-dom.production.js=6.69 kB6.69 kB+0.11%1.83 kB1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js+0.03%665.99 kB666.18 kB=117.39 kB117.35 kB
facebook-www/ReactDOM-prod.classic.js=693.36 kB693.31 kB=122.02 kB121.98 kB
facebook-www/ReactDOM-prod.modern.js=683.78 kB683.73 kB=120.40 kB120.36 kB

Significant size changes

Includes any change greater than 0.2%:

(No significant changes)

Generated by 🚫 dangerJS against b748c98

@acdlite
acdlite marked this pull request as ready for review November 10, 2025 06:33

@rickhanloniirickhanlonii left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, could we add a test? Also seems risky, is it ok to revert if it breaks the sync or could we add a killswitch for the meta builds?

Comment threadpackages/react-reconciler/src/ReactFiberCommitWork.js
Comment threadpackages/react-reconciler/src/ReactFiberCommitWork.js
Comment threadpackages/react-reconciler/src/ReactFiberCommitWork.js
return;
}
case OffscreenComponent:
case LegacyHiddenComponent: {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we omit LegacyHidden from this? Want to limit the risk of breaking changes and if they need this feature they should convert to Activity.

@acdlite

Copy link
Copy Markdown
CollaboratorAuthor

Yeah I wrote some tests just forgot to stage the file

@acdlite
acdliteforce-pushed the activity-portals-fix branch from 17ca31a to 58f2ab4CompareNovember 10, 2025 15:27
@acdlite

acdlite commented Nov 10, 2025

Copy link
Copy Markdown
CollaboratorAuthor

and yes you can revert if it breaks the Meta sync. Assuming it's due to a bug rather than due to the intentional behavior change. If something is relying on the portal not being hidden then I'd prefer not the revert and we should find some other solution for whatever that code is trying to do. (We could revert with a Meta-only flag in the meantime.)

@acdlite
acdliteforce-pushed the activity-portals-fix branch from 58f2ab4 to f2a23f2CompareNovember 10, 2025 15:34
This PR updates the behavior of Activity so that when it is hidden,
it hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit
phase lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's
no reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and
hide _any_ portal that is contained within it, and recursively hide
its direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
@acdlite
acdliteforce-pushed the activity-portals-fix branch from f2a23f2 to b748c98CompareNovember 10, 2025 15:37
@acdlite
acdlite merged commit 5268492 into react:mainNov 10, 2025
239 of 240 checks passed
github-actionsBot pushed a commit that referenced this pull request Nov 10, 2025
This PR updates the behavior of Activity so that when it is hidden, it
hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit phase
lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's no
reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and hide
_any_ portal that is contained within it, and recursively hide its
direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
DiffTrain build for [5268492](5268492)
github-actionsBot pushed a commit that referenced this pull request Nov 10, 2025
This PR updates the behavior of Activity so that when it is hidden, it
hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit phase
lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's no
reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and hide
_any_ portal that is contained within it, and recursively hide its
direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
DiffTrain build for [5268492](5268492)
manNomi pushed a commit to manNomi/react that referenced this pull request Nov 15, 2025
This PR updates the behavior of Activity so that when it is hidden, it
hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit phase
lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's no
reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and hide
_any_ portal that is contained within it, and recursively hide its
direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
@ziyak97

Copy link
Copy Markdown

Is this already out? Will this fix - vercel/next.js#85502 (reply in thread)

If it’s not out any ETA

@abed-daloopa

Copy link
Copy Markdown

When will this be released?

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

Labels

CLA SignedReact Core TeamOpened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Activity mode=“hidden” does not hide nested portals

5 participants

@acdlite@react-sizebot@ziyak97@abed-daloopa@rickhanlonii
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix: Activity should hide portal contents - #35091

Merged
acdlite merged 2 commits into
react:mainfrom
acdlite:activity-portals-fix
Nov 10, 2025
Merged

Fix: Activity should hide portal contents#35091
acdlite merged 2 commits into
react:mainfrom
acdlite:activity-portals-fix

Conversation

@acdlite

@acdliteacdlite commented Nov 10, 2025

Copy link
Copy Markdown
Collaborator

Fixes#35000

This PR updates the behavior of Activity so that when it is hidden, it hides the contents of any portals contained within it.

Previously we had intentionally chosen not to implement this behavior, because it was thought that this concern should be left to the userspace code that manages the portal, e.g. by adding or removing the portal container from the DOM. Depending on the use case for the portal, this is often desirable anyway because the portal container itself is not controlled by React.

However, React does own the contents of the portal, and we can hide those elements regardless of what the user chooses to do with the container. This makes the hiding/unhiding behavior of portals with Activity automatic in the majority of cases, and also benefits from aligning the DOM mutations with the rest of the React's commit phase lifecycle.

The reason we have to special case this at all is because usually we only hide the direct DOM children of the Activity boundary. There's no reason to go deeper than that, because hiding a parent DOM element effectively hides everything inside of it. Portals are the exception, because they don't exist in the normal DOM hierarchy; we can't assume that just because a portal has a parent in the React tree that it will also have that parent in the actual DOM.

So, whenever an Activity boundary is hidden, we must search for and hide any portal that is contained within it, and recursively hide its direct children, too.

To optimize this search, we use a new subtree flag, PortalStatic, that is set only on fiber paths that contain a HostPortal. This lets us skip over any subtree that does not contain a portal.

Matches the pattern we use for the other traversals in the commit phase.
@github-actionsgithub-actionsBot added the React Core Team Opened by a member of the React Core Team label Nov 10, 2025
@acdlite
acdliteforce-pushed the activity-portals-fix branch from b976315 to 17ca31aCompareNovember 10, 2025 06:28
@react-sizebot

react-sizebot commented Nov 10, 2025

Copy link
Copy Markdown

Comparing: c83be7d...b748c98

Critical size changes

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

Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-stable/react-dom/cjs/react-dom.production.js=6.68 kB6.68 kB+0.11%1.83 kB1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js+0.01%608.07 kB608.16 kB=107.69 kB107.64 kB
oss-experimental/react-dom/cjs/react-dom.production.js=6.69 kB6.69 kB+0.11%1.83 kB1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js+0.03%665.99 kB666.18 kB=117.39 kB117.35 kB
facebook-www/ReactDOM-prod.classic.js=693.36 kB693.31 kB=122.02 kB121.98 kB
facebook-www/ReactDOM-prod.modern.js=683.78 kB683.73 kB=120.40 kB120.36 kB

Significant size changes

Includes any change greater than 0.2%:

(No significant changes)

Generated by 🚫 dangerJS against b748c98

@acdlite
acdlite marked this pull request as ready for review November 10, 2025 06:33

@rickhanloniirickhanlonii left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, could we add a test? Also seems risky, is it ok to revert if it breaks the sync or could we add a killswitch for the meta builds?

Comment threadpackages/react-reconciler/src/ReactFiberCommitWork.js
Comment threadpackages/react-reconciler/src/ReactFiberCommitWork.js
Comment threadpackages/react-reconciler/src/ReactFiberCommitWork.js
return;
}
case OffscreenComponent:
case LegacyHiddenComponent: {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we omit LegacyHidden from this? Want to limit the risk of breaking changes and if they need this feature they should convert to Activity.

@acdlite

Copy link
Copy Markdown
CollaboratorAuthor

Yeah I wrote some tests just forgot to stage the file

@acdlite
acdliteforce-pushed the activity-portals-fix branch from 17ca31a to 58f2ab4CompareNovember 10, 2025 15:27
@acdlite

acdlite commented Nov 10, 2025

Copy link
Copy Markdown
CollaboratorAuthor

and yes you can revert if it breaks the Meta sync. Assuming it's due to a bug rather than due to the intentional behavior change. If something is relying on the portal not being hidden then I'd prefer not the revert and we should find some other solution for whatever that code is trying to do. (We could revert with a Meta-only flag in the meantime.)

@acdlite
acdliteforce-pushed the activity-portals-fix branch from 58f2ab4 to f2a23f2CompareNovember 10, 2025 15:34
This PR updates the behavior of Activity so that when it is hidden,
it hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit
phase lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's
no reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and
hide _any_ portal that is contained within it, and recursively hide
its direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
@acdlite
acdliteforce-pushed the activity-portals-fix branch from f2a23f2 to b748c98CompareNovember 10, 2025 15:37
@acdlite
acdlite merged commit 5268492 into react:mainNov 10, 2025
239 of 240 checks passed
github-actionsBot pushed a commit that referenced this pull request Nov 10, 2025
This PR updates the behavior of Activity so that when it is hidden, it
hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit phase
lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's no
reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and hide
_any_ portal that is contained within it, and recursively hide its
direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
DiffTrain build for [5268492](5268492)
github-actionsBot pushed a commit that referenced this pull request Nov 10, 2025
This PR updates the behavior of Activity so that when it is hidden, it
hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit phase
lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's no
reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and hide
_any_ portal that is contained within it, and recursively hide its
direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
DiffTrain build for [5268492](5268492)
manNomi pushed a commit to manNomi/react that referenced this pull request Nov 15, 2025
This PR updates the behavior of Activity so that when it is hidden, it
hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit phase
lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's no
reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and hide
_any_ portal that is contained within it, and recursively hide its
direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
@ziyak97

Copy link
Copy Markdown

Is this already out? Will this fix - vercel/next.js#85502 (reply in thread)

If it’s not out any ETA

@abed-daloopa

Copy link
Copy Markdown

When will this be released?

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

Labels

CLA SignedReact Core TeamOpened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Activity mode=“hidden” does not hide nested portals

5 participants

@acdlite@react-sizebot@ziyak97@abed-daloopa@rickhanlonii
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix: Activity should hide portal contents - #35091

Merged
acdlite merged 2 commits into
react:mainfrom
acdlite:activity-portals-fix
Nov 10, 2025
Merged

Fix: Activity should hide portal contents#35091
acdlite merged 2 commits into
react:mainfrom
acdlite:activity-portals-fix

Conversation

@acdlite

@acdliteacdlite commented Nov 10, 2025

Copy link
Copy Markdown
Collaborator

Fixes#35000

This PR updates the behavior of Activity so that when it is hidden, it hides the contents of any portals contained within it.

Previously we had intentionally chosen not to implement this behavior, because it was thought that this concern should be left to the userspace code that manages the portal, e.g. by adding or removing the portal container from the DOM. Depending on the use case for the portal, this is often desirable anyway because the portal container itself is not controlled by React.

However, React does own the contents of the portal, and we can hide those elements regardless of what the user chooses to do with the container. This makes the hiding/unhiding behavior of portals with Activity automatic in the majority of cases, and also benefits from aligning the DOM mutations with the rest of the React's commit phase lifecycle.

The reason we have to special case this at all is because usually we only hide the direct DOM children of the Activity boundary. There's no reason to go deeper than that, because hiding a parent DOM element effectively hides everything inside of it. Portals are the exception, because they don't exist in the normal DOM hierarchy; we can't assume that just because a portal has a parent in the React tree that it will also have that parent in the actual DOM.

So, whenever an Activity boundary is hidden, we must search for and hide any portal that is contained within it, and recursively hide its direct children, too.

To optimize this search, we use a new subtree flag, PortalStatic, that is set only on fiber paths that contain a HostPortal. This lets us skip over any subtree that does not contain a portal.

Matches the pattern we use for the other traversals in the commit phase.
@github-actionsgithub-actionsBot added the React Core Team Opened by a member of the React Core Team label Nov 10, 2025
@acdlite
acdliteforce-pushed the activity-portals-fix branch from b976315 to 17ca31aCompareNovember 10, 2025 06:28
@react-sizebot

react-sizebot commented Nov 10, 2025

Copy link
Copy Markdown

Comparing: c83be7d...b748c98

Critical size changes

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

Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-stable/react-dom/cjs/react-dom.production.js=6.68 kB6.68 kB+0.11%1.83 kB1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js+0.01%608.07 kB608.16 kB=107.69 kB107.64 kB
oss-experimental/react-dom/cjs/react-dom.production.js=6.69 kB6.69 kB+0.11%1.83 kB1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js+0.03%665.99 kB666.18 kB=117.39 kB117.35 kB
facebook-www/ReactDOM-prod.classic.js=693.36 kB693.31 kB=122.02 kB121.98 kB
facebook-www/ReactDOM-prod.modern.js=683.78 kB683.73 kB=120.40 kB120.36 kB

Significant size changes

Includes any change greater than 0.2%:

(No significant changes)

Generated by 🚫 dangerJS against b748c98

@acdlite
acdlite marked this pull request as ready for review November 10, 2025 06:33

@rickhanloniirickhanlonii left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, could we add a test? Also seems risky, is it ok to revert if it breaks the sync or could we add a killswitch for the meta builds?

Comment threadpackages/react-reconciler/src/ReactFiberCommitWork.js
Comment threadpackages/react-reconciler/src/ReactFiberCommitWork.js
Comment threadpackages/react-reconciler/src/ReactFiberCommitWork.js
return;
}
case OffscreenComponent:
case LegacyHiddenComponent: {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we omit LegacyHidden from this? Want to limit the risk of breaking changes and if they need this feature they should convert to Activity.

@acdlite

Copy link
Copy Markdown
CollaboratorAuthor

Yeah I wrote some tests just forgot to stage the file

@acdlite
acdliteforce-pushed the activity-portals-fix branch from 17ca31a to 58f2ab4CompareNovember 10, 2025 15:27
@acdlite

acdlite commented Nov 10, 2025

Copy link
Copy Markdown
CollaboratorAuthor

and yes you can revert if it breaks the Meta sync. Assuming it's due to a bug rather than due to the intentional behavior change. If something is relying on the portal not being hidden then I'd prefer not the revert and we should find some other solution for whatever that code is trying to do. (We could revert with a Meta-only flag in the meantime.)

@acdlite
acdliteforce-pushed the activity-portals-fix branch from 58f2ab4 to f2a23f2CompareNovember 10, 2025 15:34
This PR updates the behavior of Activity so that when it is hidden,
it hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit
phase lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's
no reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and
hide _any_ portal that is contained within it, and recursively hide
its direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
@acdlite
acdliteforce-pushed the activity-portals-fix branch from f2a23f2 to b748c98CompareNovember 10, 2025 15:37
@acdlite
acdlite merged commit 5268492 into react:mainNov 10, 2025
239 of 240 checks passed
github-actionsBot pushed a commit that referenced this pull request Nov 10, 2025
This PR updates the behavior of Activity so that when it is hidden, it
hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit phase
lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's no
reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and hide
_any_ portal that is contained within it, and recursively hide its
direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
DiffTrain build for [5268492](5268492)
github-actionsBot pushed a commit that referenced this pull request Nov 10, 2025
This PR updates the behavior of Activity so that when it is hidden, it
hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit phase
lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's no
reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and hide
_any_ portal that is contained within it, and recursively hide its
direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
DiffTrain build for [5268492](5268492)
manNomi pushed a commit to manNomi/react that referenced this pull request Nov 15, 2025
This PR updates the behavior of Activity so that when it is hidden, it
hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit phase
lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's no
reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and hide
_any_ portal that is contained within it, and recursively hide its
direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
@ziyak97

Copy link
Copy Markdown

Is this already out? Will this fix - vercel/next.js#85502 (reply in thread)

If it’s not out any ETA

@abed-daloopa

Copy link
Copy Markdown

When will this be released?

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

Labels

CLA SignedReact Core TeamOpened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Activity mode=“hidden” does not hide nested portals

5 participants

@acdlite@react-sizebot@ziyak97@abed-daloopa@rickhanlonii
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Fix: Activity should hide portal contents - #35091

Merged
acdlite merged 2 commits into
react:mainfrom
acdlite:activity-portals-fix
Nov 10, 2025
Merged

Fix: Activity should hide portal contents#35091
acdlite merged 2 commits into
react:mainfrom
acdlite:activity-portals-fix

Conversation

@acdlite

@acdliteacdlite commented Nov 10, 2025

Copy link
Copy Markdown
Collaborator

Fixes#35000

This PR updates the behavior of Activity so that when it is hidden, it hides the contents of any portals contained within it.

Previously we had intentionally chosen not to implement this behavior, because it was thought that this concern should be left to the userspace code that manages the portal, e.g. by adding or removing the portal container from the DOM. Depending on the use case for the portal, this is often desirable anyway because the portal container itself is not controlled by React.

However, React does own the contents of the portal, and we can hide those elements regardless of what the user chooses to do with the container. This makes the hiding/unhiding behavior of portals with Activity automatic in the majority of cases, and also benefits from aligning the DOM mutations with the rest of the React's commit phase lifecycle.

The reason we have to special case this at all is because usually we only hide the direct DOM children of the Activity boundary. There's no reason to go deeper than that, because hiding a parent DOM element effectively hides everything inside of it. Portals are the exception, because they don't exist in the normal DOM hierarchy; we can't assume that just because a portal has a parent in the React tree that it will also have that parent in the actual DOM.

So, whenever an Activity boundary is hidden, we must search for and hide any portal that is contained within it, and recursively hide its direct children, too.

To optimize this search, we use a new subtree flag, PortalStatic, that is set only on fiber paths that contain a HostPortal. This lets us skip over any subtree that does not contain a portal.

Matches the pattern we use for the other traversals in the commit phase.
@github-actionsgithub-actionsBot added the React Core Team Opened by a member of the React Core Team label Nov 10, 2025
@acdlite
acdliteforce-pushed the activity-portals-fix branch from b976315 to 17ca31aCompareNovember 10, 2025 06:28
@react-sizebot

react-sizebot commented Nov 10, 2025

Copy link
Copy Markdown

Comparing: c83be7d...b748c98

Critical size changes

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

Name+/-BaseCurrent+/- gzipBase gzipCurrent gzip
oss-stable/react-dom/cjs/react-dom.production.js=6.68 kB6.68 kB+0.11%1.83 kB1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js+0.01%608.07 kB608.16 kB=107.69 kB107.64 kB
oss-experimental/react-dom/cjs/react-dom.production.js=6.69 kB6.69 kB+0.11%1.83 kB1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js+0.03%665.99 kB666.18 kB=117.39 kB117.35 kB
facebook-www/ReactDOM-prod.classic.js=693.36 kB693.31 kB=122.02 kB121.98 kB
facebook-www/ReactDOM-prod.modern.js=683.78 kB683.73 kB=120.40 kB120.36 kB

Significant size changes

Includes any change greater than 0.2%:

(No significant changes)

Generated by 🚫 dangerJS against b748c98

@acdlite
acdlite marked this pull request as ready for review November 10, 2025 06:33

@rickhanloniirickhanlonii left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, could we add a test? Also seems risky, is it ok to revert if it breaks the sync or could we add a killswitch for the meta builds?

Comment threadpackages/react-reconciler/src/ReactFiberCommitWork.js
Comment threadpackages/react-reconciler/src/ReactFiberCommitWork.js
Comment threadpackages/react-reconciler/src/ReactFiberCommitWork.js
return;
}
case OffscreenComponent:
case LegacyHiddenComponent: {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we omit LegacyHidden from this? Want to limit the risk of breaking changes and if they need this feature they should convert to Activity.

@acdlite

Copy link
Copy Markdown
CollaboratorAuthor

Yeah I wrote some tests just forgot to stage the file

@acdlite
acdliteforce-pushed the activity-portals-fix branch from 17ca31a to 58f2ab4CompareNovember 10, 2025 15:27
@acdlite

acdlite commented Nov 10, 2025

Copy link
Copy Markdown
CollaboratorAuthor

and yes you can revert if it breaks the Meta sync. Assuming it's due to a bug rather than due to the intentional behavior change. If something is relying on the portal not being hidden then I'd prefer not the revert and we should find some other solution for whatever that code is trying to do. (We could revert with a Meta-only flag in the meantime.)

@acdlite
acdliteforce-pushed the activity-portals-fix branch from 58f2ab4 to f2a23f2CompareNovember 10, 2025 15:34
This PR updates the behavior of Activity so that when it is hidden,
it hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit
phase lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's
no reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and
hide _any_ portal that is contained within it, and recursively hide
its direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
@acdlite
acdliteforce-pushed the activity-portals-fix branch from f2a23f2 to b748c98CompareNovember 10, 2025 15:37
@acdlite
acdlite merged commit 5268492 into react:mainNov 10, 2025
239 of 240 checks passed
github-actionsBot pushed a commit that referenced this pull request Nov 10, 2025
This PR updates the behavior of Activity so that when it is hidden, it
hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit phase
lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's no
reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and hide
_any_ portal that is contained within it, and recursively hide its
direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
DiffTrain build for [5268492](5268492)
github-actionsBot pushed a commit that referenced this pull request Nov 10, 2025
This PR updates the behavior of Activity so that when it is hidden, it
hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit phase
lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's no
reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and hide
_any_ portal that is contained within it, and recursively hide its
direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
DiffTrain build for [5268492](5268492)
manNomi pushed a commit to manNomi/react that referenced this pull request Nov 15, 2025
This PR updates the behavior of Activity so that when it is hidden, it
hides the contents of any portals contained within it.
Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.
However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit phase
lifecycle.
The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's no
reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.
So, whenever an Activity boundary is hidden, we must search for and hide
_any_ portal that is contained within it, and recursively hide its
direct children, too.
To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
@ziyak97

Copy link
Copy Markdown

Is this already out? Will this fix - vercel/next.js#85502 (reply in thread)

If it’s not out any ETA

@abed-daloopa

Copy link
Copy Markdown

When will this be released?

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

Labels

CLA SignedReact Core TeamOpened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Activity mode=“hidden” does not hide nested portals

5 participants

@acdlite@react-sizebot@ziyak97@abed-daloopa@rickhanlonii