fix(ios): reset EmptyLayoutMetrics on Fabric view recycle#57590
Open
kosmydel wants to merge 1 commit into
Open
fix(ios): reset EmptyLayoutMetrics on Fabric view recycle#57590kosmydel wants to merge 1 commit into
EmptyLayoutMetrics on Fabric view recycle#57590kosmydel wants to merge 1 commit into
Conversation
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>
kosmydel
marked this pull request as ready for review
July 17, 2026 09:56
EmptyLayoutMetrics on Fabric view recycle
|
@javache has imported this pull request. If you are a Meta employee, you can view this in D112528840. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary:
On iOS Fabric, a recycled
RCTViewComponentViewcan keepUIView.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 withUIView.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=YESand is later reused for visible Flex content. On reuse,RCTViewComponentViewignores the mounting manager’soldLayoutMetricsargument and passes its stored_layoutMetricsinstead. AfterprepareForRecyclethat stored value is zero-init Flex ({}), notEmptyLayoutMetrics, so Flex→Flex skips updatinghidden. 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 viaUIView.hidden, or any other path that setshiddenbefore 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
prepareForRecycleleaks (e.g. ScrollView inset/offset). The Insert path already treatsEmptyLayoutMetricsas “apply all layout-derived UIView state”; recycle should leave that same sentinel so remounts stay correct when recycling is used.RCTViewComponentView prepareForRecyclecurrently does:_layoutMetrics = {};That is not
EmptyLayoutMetrics(frame-1×-1).UIView+ComponentViewProtocol updateLayoutMetricsonly force-updatesUIView.hiddenwhenoldLayoutMetrics == EmptyLayoutMetrics:Fix: assign
EmptyLayoutMetricsinprepareForRecycleso the next mount force-updateshidden(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:
npm install && cd ios && bundle exec pod install && cd .. && npm run iosBefore this change (
shouldBeRecycled = YES, default):FAIL — recycled view still hidden=YES (1/1)With
+shouldBeRecycled { return NO; }(workaround):PASS) — confirms failure is recycle reuse, not JS unmountAfter this change (rebuild RN from source / with the patch):
PASSwith recycling still enabled