Skip to content

Commit 03621fd

Browse files
mozziusclaude
andcommitted
Fix iOS removeClippedSubviews being lost on recycled views
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent bf62cce commit 03621fd

2 files changed

Lines changed: 23 additions & 1 deletion

File tree

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,9 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
359359

360360
// Disable `removeClippedSubviews` when Fabric View Culling is enabled.
361361
if (!ReactNativeFeatureFlags::enableViewCulling()) {
362-
if (oldViewProps.removeClippedSubviews != newViewProps.removeClippedSubviews) {
362+
// Compare against the current state, not `_props`: `prepareForRecycle` resets
363+
// `_removeClippedSubviews` but keeps `_props`.
364+
if (_removeClippedSubviews != newViewProps.removeClippedSubviews) {
363365
_removeClippedSubviews = newViewProps.removeClippedSubviews;
364366
[self _updateRemoveClippedSubviewsState];
365367
}

‎packages/react-native/React/Tests/Mounting/RCTViewComponentViewTests.mm‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,26 @@ - (void)testUnmountAfterToggleOffCleansUpReactSubviews
143143
XCTAssertNil(child2.superview);
144144
}
145145

146+
- (void)testRemoveClippedSubviewsIsKeptOnRecycledView
147+
{
148+
RCTViewComponentView *view = [RCTViewComponentView new];
149+
[view updateProps:makeViewProps(true) oldProps:ViewShadowNode::defaultSharedProps()];
150+
[view prepareForRecycle];
151+
152+
// Reused for another view with removeClippedSubviews. Like the mounting layer
153+
// does for an Insert, pass no old props: the view diffs against its own.
154+
[view updateProps:makeViewProps(true) oldProps:nullptr];
155+
156+
UIView *child = [UIView new];
157+
child.frame = CGRectMake(0, 400, 50, 50);
158+
[view mountChildComponentView:(id)child index:0];
159+
160+
// Clipping is on, so the child is tracked and left for the clipping pass to attach.
161+
XCTAssertNil(child.superview);
162+
NSMutableArray *reactSubviews = [view valueForKey:@"_reactSubviews"];
163+
XCTAssertEqual(reactSubviews.count, 1u);
164+
}
165+
146166
#pragma mark - hitTest against non-invertible transforms (#50797)
147167

148168
- (void)testHitTestReturnsNilForZeroScaleYView

0 commit comments

Comments
 (0)