Repository navigation
Description
Description
On the New Architecture, when a view's style changes from a definite main-axis size (e.g. width: 300 in a row) to a style that resolves a flex basis (flex: 1, i.e. basis 0), the view keeps its previous width as its flex basis. Because RN's flex: 1 has flexShrink: 0, the view then never shrinks: in a container narrower than the old width it overflows. In a wider container flexGrow hides the problem.
We hit it in a two-pane layout: the list pane is width: 475 while two panes are shown and flex: 1 when only one is. When the window shrank to 469 pt (iPhone Duo, two apps side by side) the list stayed 475 pt wide and overflowed the window. React sends the right update to native (cloneNodeWithNewChildrenAndProps with {"flex":1,"width":null,...}), but Yoga lays the node out at the old width and no further onLayout fires.
Cause
YogaLayoutableShadowNode::updateYogaProps() applies a changed style with yogaNode_.setStyle(...) + yogaNode_.setDirty(true). Yoga's own style setters (YGNodeStyleSet*) call Node::markDirtyAndPropagate(), which also clears layout.computedFlexBasis; Node::setDirty() does not. In computeFlexBasisForChild, the resolved-flex-basis branch only computes a basis when computedFlexBasis is undefined (unless WebFlexBasis is on), so the basis cached while the node had width: 300 is reused after the style becomes flex: 1.
This code is unchanged in v0.88.1 and on main.
Related but different: #58294 / react/yoga#2019 (fix proposed in react/yoga#2020). There the node's style never changes; a basis measured in an earlier layout survives because an ancestor's measurement cache skips the subtree on rotation. Here the node's own style changes, and the stale value is the basis resolved from its old width (the style-dimension branch). react/yoga#2020 marks that kind of basis as not measured and keeps retaining it, so it does not cover this case.
Suggested fix
In YogaLayoutableShadowNode::updateYogaProps(), when the style changed, also clear the cached basis, matching what markDirtyAndPropagate() does:
const bool styleChanged = styleResult != yogaNode_.style();
if (!YGNodeIsDirty(&yogaNode_) && styleChanged) {
yogaNode_.setDirty(true);
}
if (styleChanged) {
yogaNode_.setLayoutComputedFlexBasis(yoga::FloatOptional());
}
yogaNode_.setStyle(styleResult);Happy to open a PR.
Yoga-only reproduction
The same thing happens with RN's vendored Yoga alone when the style is applied the way Fabric applies it (setStyle() + setDirty()):
#include <yoga/Yoga.h>
#include <yoga/node/Node.h>
#include <cstdio>
using namespace facebook::yoga;
int main() {
YGNodeRef root = YGNodeNew();
YGNodeStyleSetFlexDirection(root, YGFlexDirectionRow);
YGNodeStyleSetWidth(root, 951);
YGNodeStyleSetHeight(root, 600);
YGNodeRef list = YGNodeNew();
YGNodeStyleSetWidth(list, 475);
YGNodeInsertChild(root, list, 0);
YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR);
// What Fabric does on a React commit: new styles via setStyle() + setDirty().
Node* r = resolveRef(root);
Node* l = resolveRef(list);
Style rs = r->style();
rs.setDimension(Dimension::Width, StyleSizeLength::points(469));
r->setStyle(rs);
r->setDirty(true);
Style ls = l->style();
ls.setDimension(Dimension::Width, StyleSizeLength::undefined());
ls.setFlex(FloatOptional(1));
l->setStyle(ls);
l->setDirty(true);
YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR);
printf("list width = %.0f (row = 469)\n", YGNodeLayoutGetWidth(list)); // prints 475
}| How the style is applied | row 469 | row 385 | row 669 |
|---|---|---|---|
setStyle() + setDirty() (Fabric today) |
475 | 475 | 669 |
YGNodeStyleSetWidth(YGUndefined) + YGNodeStyleSetFlex(1) |
469 | 385 | 669 |
Fabric path + setLayoutComputedFlexBasis(FloatOptional()) |
469 | 385 | 669 |
Steps to reproduce
- Clone the reproducer and run it (
yarn install, thenyarn androidoryarn ios). - Each row starts 360 wide with a 300-wide red child. Press Shrink rows to 200. This shrinks both rows to 200 in one commit and changes the children's style:
- top row:
width: 300→flex: 1 - bottom row (control):
width: 300→width: '100%'
- top row:
- Expected: both children fit the row (196 inside the 2 pt border).
Actual: theflex: 1child stays 300 wide and overflows the 200 row; the control child is 196. Pressing the button again (back to 360) and once more reproduces it every time.
React Native Version
0.88.1
Affected Platforms
Runtime - Android, Runtime - iOS
Areas
Fabric - The New Renderer
Output of npx @react-native-community/cli info
System:
OS: macOS 27.0.1
CPU: (10) arm64 Apple M1 Pro
Memory: 66.84 MB / 32.00 GB
Shell:
version: "5.9"
path: /bin/zsh
Binaries:
Node:
version: 22.23.0
path: ~/.nvm/versions/node/v22.23.0/bin/node
Yarn:
version: 1.22.22
path: /opt/homebrew/bin/yarn
npm:
version: 10.9.8
path: ~/.nvm/versions/node/v22.23.0/bin/npm
Watchman:
version: 2025.10.27.00
path: /opt/homebrew/bin/watchman
Managers:
CocoaPods:
version: 1.16.2
path: /opt/homebrew/bin/pod
SDKs:
iOS SDK:
Platforms:
- DriverKit 27.2
- iOS 27.2
- macOS 27.2
- tvOS 27.2
- visionOS 27.2
- watchOS 27.2
Android SDK:
API Levels:
- "36"
- "37"
Build Tools:
- 35.0.0
- 36.0.0
- 36.1.0
- 37.0.0
System Images:
- android-36.1 | Google Play ARM 64 v8a
Android NDK: Not Found
IDEs:
Android Studio: 2025.1 AI-251.26094.121.2513.14007798
Xcode:
version: 27.2/27B52
path: /usr/bin/xcodebuild
Languages:
Java:
version: 21.0.7
path: /usr/bin/javac
Ruby:
version: 2.6.10
path: /usr/bin/ruby
npmPackages:
"@react-native-community/cli":
installed: 20.2.0
wanted: 20.2.0
react:
installed: 19.3.0
wanted: 19.3.0
react-native:
installed: 0.88.1
wanted: 0.88.1
react-native-macos: Not Found
npmGlobalPackages:
"*react-native*": Not Found
Android:
hermesEnabled: true
newArchEnabled: true
iOS:
hermesEnabled: Not found
newArchEnabled: false
(Run on the Android reproducer; the iOS section shows defaults because Pods were not installed for this run.)
Stacktrace or Logs
No crash. Values printed by the reproducer after pressing the button:
row 200, child 300: BUG (child wider than row) <- width: 300 -> flex: 1
row 200, child 196: OK <- width: 300 -> width: '100%' (control)
MANDATORY Reproducer
https://github.com/wil-li-la/rn-stale-flex-basis-repro
Screenshots and Videos
Android emulator (API 36.1), RN 0.88.1 release build. Left: before. Right: after Shrink rows to 200.
We first saw it on iOS (iOS 27.1 simulator, RN 0.81.5 via Expo SDK 54, New Architecture). The reproducer above was verified on Android with 0.88.1; the code involved is shared C++ and unchanged between those versions.

Activity
dave-otf commentedon Nov 2, 2026
The table separates this from the measured-basis case in #58294/#2020. A regression test for the Fabric
setStyle()+setDirty()path using the two-row width-to-flex: 1repro would pin it: after the row shrinks to 200, the flex child should fit inside the bordered row like the100%control, then still grow when restored to 360.