Skip to content

Do not retain a flex basis measured during a previous layout - #2020

Open
mozzius wants to merge 1 commit into
react:mainfrom
mozzius:fix-stale-measured-flex-basis
Open

Do not retain a flex basis measured during a previous layout#2020
mozzius wants to merge 1 commit into
react:mainfrom
mozzius:fix-stale-measured-flex-basis

Conversation

@mozzius

@mozzius mozzius commented Sep 2, 2026

Copy link
Copy Markdown

Summary

Fixes #2019.

layout.computedFlexBasis persists across YGNodeCalculateLayout calls and is only cleared by markDirtyAndPropagate on that exact node. The resolved-flex-basis branch of computeFlexBasisForChild keeps whatever value is already stored, which is what lets a max-content measurement made earlier in the same layout survive into the definite-size pass (and is why flex: 1 inside an auto-height column means "content height"). The guard only checks whether a value exists, not which layout produced it.

When a later layout at a different root size hits an ancestor's measurement cache during the max-content pass, that subtree is skipped, the child's computedFlexBasis is never refreshed, and the definite pass retains a size measured against the previous available size. In React Native this shows up as a flex: 1 Text that keeps its landscape height after rotating back to portrait (react/react-native#58294).

Fix

Record whether a stored computedFlexBasis came from measuring the child's content (computedFlexBasisIsMeasured). In the resolved branch, a stored value that is measured and stamped with a previous generation is treated as stale: the child takes the measure path again, which does exactly what the skipped max-content pass would have done and normally hits the child's own measurement cache. A retained resolved basis is unchanged, so "definite from the start" containers still resolve flex: 1 to a basis of 0 as before, and nothing changes within a single layout.

Clearing the value instead (or enabling WebFlexBasis) is not a fix: the resolved branch then writes 0 with no measurement to follow and the child collapses to 0.

Test plan

  • New test in tests/YGRelayoutTest.cpp: an 8-node tree (scroll container, flex: 1 column with a fixed-height sibling, two plain wrappers, a flex: 1 node with a text-like measure function) laid out at 400x800, 800x400, 400x800. Asserts the measured node returns to its first height. Fails on main, passes with this change.
  • ./unit_tests Debug: all 843 tests pass. On main the new test fails (portraitHeight is 100, the relaid-out node is 60) and the other 842 pass.
  • Standalone repro (https://github.com/mozzius/rotation-text-repro, yoga-harness/run.sh) built against this branch: two and three wrapper levels now return to their first height (they stay at the landscape height on main); one wrapper level passes on both.

`computedFlexBasis` survives across layout calls, and the resolved-basis
branch of `computeFlexBasisForChild` keeps whatever is stored so a size
measured by the max-content pass carries into the definite-size pass.
That guard checks whether a value exists, not which layout produced it:
when an ancestor's measurement cache answers for a whole subtree the
skipped max-content pass leaves a basis measured against a different
available size, and a `flex: 1` node keeps its landscape height after
rotating back to portrait. Track whether a stored basis was measured,
and re-measure rather than retain a stale one from an earlier layout.

Fixes react#2019
@meta-cla meta-cla Bot added the CLA Signed label Sep 2, 2026
@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stale computedFlexBasis is reused across layouts: a flex: 1 node keeps its previous size after relayout at a new root size

1 participant