Skip to content

Commit 711a9a5

Browse files
kosmydelmeta-codesync[bot]
authored andcommitted
fix(ios): reset EmptyLayoutMetrics on Fabric view recycle (#57590)
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: ```objc _layoutMetrics = {}; ``` That is **not** `EmptyLayoutMetrics` (frame `-1×-1`). `UIView+ComponentViewProtocol updateLayoutMetrics` only force-updates `UIView.hidden` when `oldLayoutMetrics == EmptyLayoutMetrics`: ```objc 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 Pull Request resolved: #57590 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 Reviewed By: christophpurrer Differential Revision: D112528840 Pulled By: javache fbshipit-source-id: 5ec522d8f00c4406253025998718971852fa79a5
1 parent ad870da commit 711a9a5

1 file changed

Lines changed: 1 addition & 1 deletion

File tree

packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -723,7 +723,7 @@ - (void)prepareForRecycle
723723
_isJSResponder = NO;
724724
_removeClippedSubviews = NO;
725725
_reactSubviews = [NSMutableArray new];
726-
_layoutMetrics = {};
726+
_layoutMetrics = EmptyLayoutMetrics;
727727
}
728728

729729
- (void)setPropKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN:(NSSet<NSString *> *_Nullable)props

0 commit comments

Comments
 (0)