Skip to content

Commit bc47849

Browse files
Yonah Karpmeta-codesync[bot]
authored andcommitted
Fix dirtied callback firing on removed children (facebook#55717)
Summary: Pull Request resolved: facebook#55717 As per Claude: ## Root Cause D92280506 added excludedChild->setDirty(true) to YGNodeRemoveChild and YGNodeRemoveAllChildren. The setDirty(true) call fires the dirtiedFunc_ callback, which causes a chain reaction that makes text disappear in React Native's legacy architecture. ## The Bug Chain 1. sizeThatFitsMinimumSize: (RCTShadowView:368-403) measures inline views by cloning their Yoga node via YGNodeClone. The clone inherits dirtiedFunc_ and context_ from the original. 2. After measurement, YGNodeRemoveChild (line 391) removes the clone from the temp constraint node. The new setDirty(true) fires dirtiedFunc_ on the clone. 3. The clone's dirtiedFunc_ is RCTInlineViewYogaNodeDirtied (RCTBaseTextShadowView:20-31), set when inline views are inserted into text nodes (line 59). The callback reads context_ from the clone — which points to the real inline view — gets its React superview (the text node), and calls [baseTextShadowView dirtyLayout]. 4. dirtyLayout (RCTTextShadowView:58-63) calls YGNodeMarkDirty(self.yogaNode), marking the text node dirty after Yoga had already cleared the dirty flag. 5. When uiManagerWillPerformMounting runs (RCTTextShadowView:73-77), the guard if (YGNodeIsDirty(self.yogaNode)) { return; } bails out — text is never pushed to the native view. #### Why setLayout({}) alone was sufficient setLayout({}) resets generationCount to 0, which forces the Yoga layout algorithm to fully recalculate the node on the next layout pass (the needToVisitNode check in CalculateLayout.cpp:2167 compares generationCount against the current generation). The dirty flag is redundant for layout correctness but was intended as a semantic indicator. ## Fix Suppress the dirtiedFunc_ callback when marking removed children dirty. The dirty flag should still be set (for API correctness when checking YGNodeIsDirty), but the callback must not fire because the node is being detached from the tree. Changelog: [Internal] Reviewed By: NickGerleman Differential Revision: D93010183 fbshipit-source-id: a81b4ce6587b81cb317e670a310f43920896805b
1 parent 7d2d3a2 commit bc47849

1 file changed

Lines changed: 14 additions & 2 deletions

File tree

  • packages/react-native/ReactCommon/yoga/yoga

packages/react-native/ReactCommon/yoga/yoga/YGNode.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,13 @@ void YGNodeRemoveChild(
177177
if (owner == childOwner) {
178178
excludedChild->setLayout({}); // layout is no longer valid
179179
excludedChild->setOwner(nullptr);
180-
excludedChild->setDirty(true); // invalidate cache
180+
// Mark dirty to invalidate cache, but suppress the dirtied callback
181+
// since the node is being detached from the tree and should not
182+
// propagate dirty signals through external callback mechanisms.
183+
auto dirtiedFunc = excludedChild->getDirtiedFunc();
184+
excludedChild->setDirtiedFunc(nullptr);
185+
excludedChild->setDirty(true);
186+
excludedChild->setDirtiedFunc(dirtiedFunc);
181187
}
182188
owner->markDirtyAndPropagate();
183189
}
@@ -199,7 +205,13 @@ void YGNodeRemoveAllChildren(const YGNodeRef ownerRef) {
199205
yoga::Node* oldChild = owner->getChild(i);
200206
oldChild->setLayout({}); // layout is no longer valid
201207
oldChild->setOwner(nullptr);
202-
oldChild->setDirty(true); // invalidate cache
208+
// Mark dirty to invalidate cache, but suppress the dirtied callback
209+
// since the node is being detached from the tree and should not
210+
// propagate dirty signals through external callback mechanisms.
211+
auto dirtiedFunc = oldChild->getDirtiedFunc();
212+
oldChild->setDirtiedFunc(nullptr);
213+
oldChild->setDirty(true);
214+
oldChild->setDirtiedFunc(dirtiedFunc);
203215
}
204216
owner->clearChildren();
205217
owner->markDirtyAndPropagate();

0 commit comments

Comments
 (0)