Skip to content

fix(ios): reset EmptyLayoutMetrics on Fabric view recycle#57590

Open
kosmydel wants to merge 1 commit into
react:mainfrom
discord:kosmydel/upstream-fix-fabric-recycle-hidden
Open

fix(ios): reset EmptyLayoutMetrics on Fabric view recycle#57590
kosmydel wants to merge 1 commit into
react:mainfrom
discord:kosmydel/upstream-fix-fabric-recycle-hidden

Conversation

@kosmydel

@kosmydel kosmydel commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary:

On iOS Fabric, a recycled RCTViewComponentView can keep UIView.hidden = YES after reuse, so remounted visible content stays blank (React mounted, native host invisible).

On default stock iOS, this usually does not show up in normal RN usage. The mount slicer skips Trait::Hidden (display: 'none'), so those nodes are torn down rather than left as hosts with UIView.hidden = YES, and the recycle pool is not poisoned that way.

The bug is still real whenever a host does enter the pool with hidden=YES and is later reused for visible Flex content. On reuse, RCTViewComponentView ignores the mounting manager’s oldLayoutMetrics argument and passes its stored _layoutMetrics instead. After prepareForRecycle that stored value is zero-init Flex ({}), not EmptyLayoutMetrics, so Flex→Flex skips updating hidden. React still mounts/layouts; the native view stays blank.

That can happen in apps (happens in one of our biggest client's app) that keep display: 'none' mounted and hide via UIView.hidden, or any other path that sets hidden before unmount. Once poisoned, default View recycling reproduces blank UI. Confirmed workaround: +shouldBeRecycled { return NO; } (mitigation only).

This is recycle hygiene in the same family as other prepareForRecycle leaks (e.g. ScrollView inset/offset). The Insert path already treats EmptyLayoutMetrics as “apply all layout-derived UIView state”; recycle should leave that same sentinel so remounts stay correct when recycling is used.

RCTViewComponentView prepareForRecycle currently does:

_layoutMetrics = {};

That is not EmptyLayoutMetrics (frame -1×-1). UIView+ComponentViewProtocol updateLayoutMetrics only force-updates UIView.hidden when oldLayoutMetrics == EmptyLayoutMetrics:

bool forceUpdate = oldLayoutMetrics == EmptyLayoutMetrics;
if (forceUpdate || (layoutMetrics.displayType != oldLayoutMetrics.displayType)) {
  self.hidden = layoutMetrics.displayType == DisplayType::None;
}

Fix: assign EmptyLayoutMetrics in prepareForRecycle so the next mount force-updates hidden (and other layout-gated UIView state) correctly. One-line change; no API surface change.

Introduced in: #55796

Minimal repro: https://github.com/kosmydel/rn-fabric-recycle-hidden

Changelog:

[iOS] [Fixed] - Reset EmptyLayoutMetrics in RCTViewComponentView prepareForRecycle so recycled views clear UIView.hidden

Test Plan:

  1. Clone https://github.com/kosmydel/rn-fabric-recycle-hidden (RN 0.86, New Arch / iOS).
  2. npm install && cd ios && bundle exec pod install && cd .. && npm run ios
  3. Tap Run (or wait for auto-run).

Before this change (shouldBeRecycled = YES, default):

  • Expected: red square visible after remount
  • Actual: gray empty slot; status FAIL — recycled view still hidden=YES (1/1)

With +shouldBeRecycled { return NO; } (workaround):

  • Red square visible (PASS) — confirms failure is recycle reuse, not JS unmount

After this change (rebuild RN from source / with the patch):

  • Same repro shows red square + PASS with recycling still enabled

Value-init `{}` is not EmptyLayoutMetrics, so recycled display:none views
could keep self.hidden=YES when reused for visible content.

Co-authored-by: Cursor <cursoragent@cursor.com>
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jul 17, 2026
@kosmydel
kosmydel marked this pull request as ready for review July 17, 2026 09:56
@kosmydel kosmydel changed the title fix(ios): reset EmptyLayoutMetrics on Fabric view recycle fix(ios): reset EmptyLayoutMetrics on Fabric view recycle Jul 17, 2026
@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Jul 17, 2026
@meta-codesync

meta-codesync Bot commented Jul 17, 2026

Copy link
Copy Markdown

@javache has imported this pull request. If you are a Meta employee, you can view this in D112528840.

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

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant