Commit 711a9a5
fix(ios): reset
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: 5ec522d8f00c4406253025998718971852fa79a5EmptyLayoutMetrics on Fabric view recycle (#57590)1 parent ad870da commit 711a9a5
1 file changed
Lines changed: 1 addition & 1 deletion
Lines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
723 | 723 | | |
724 | 724 | | |
725 | 725 | | |
726 | | - | |
| 726 | + | |
727 | 727 | | |
728 | 728 | | |
729 | 729 | | |
| |||
0 commit comments