Skip to content

Commit adb184c

Browse files
Fix uncontrolled multiline TextInput not resizing when children change
## Summary When a multiline TextInput uses children (attributed text) instead of the `value` prop (uncontrolled mode), changing the children does not cause the TextInput to resize. For example, clearing children after sending a message leaves the input at its expanded multi-line height. ## Root Cause `measureContent()` is called by Yoga during `YGNodeCalculateLayout`, which runs BEFORE `updateStateIfNeeded()` in the `layout()` callback. In `attributedStringBoxToMeasure()`, when the state is meaningful and the `attributedStringBox` is non-empty, it returns the stale native text for measurement — even though the React tree (children) has already changed. The sequence: 1. User types multiline text → native `_updateState()` updates `attributedStringBox` with the typed text 2. User clears text (e.g. sends message) → React children become empty 3. Yoga calls `measureContent()` → `attributedStringBoxToMeasure()` returns the stale multi-line text → height stays expanded 4. `updateStateIfNeeded()` runs later and updates state, but the layout has already been computed with the wrong size ## Fix In `attributedStringBoxToMeasure()`, check if the React tree attributed string has diverged from what's stored in state. If so, use the React tree version for measurement instead of the stale `attributedStringBox`. This ensures Yoga measures with the correct content when children change between layout passes. ## Changelog [iOS][Fixed] - Fix uncontrolled multiline TextInput not resizing when children change Fixes #54570
1 parent 2d78a39 commit adb184c

File tree

1 file changed

+17
-0
lines changed

1 file changed

+17
-0
lines changed

packages/react-native/ReactCommon/react/renderer/components/textinput/BaseTextInputShadowNode.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,23 @@ class BaseTextInputShadowNode
204204
layoutContext.fontSizeMultiplier;
205205
if (meaningfulState) {
206206
const auto &stateData = BaseShadowNode::getStateData();
207+
const auto &props = BaseShadowNode::getConcreteProps();
208+
209+
// For uncontrolled TextInputs (no value prop), the react tree attributed
210+
// string may have diverged from state if children changed since the last
211+
// native state update. In that case, prefer the react tree for measurement
212+
// since attributedStringBox may contain stale native text.
213+
if (props.text.empty()) {
214+
const auto &reactTreeAttributedString = getAttributedString(layoutContext);
215+
if (!stateData.reactTreeAttributedString.isContentEqual(reactTreeAttributedString)) {
216+
auto attributedString = reactTreeAttributedString;
217+
if (attributedString.isEmpty()) {
218+
attributedString = getPlaceholderAttributedString(layoutContext);
219+
}
220+
return AttributedStringBox{attributedString};
221+
}
222+
}
223+
207224
auto attributedStringBox = stateData.attributedStringBox;
208225
if (attributedStringBox.getMode() == AttributedStringBox::Mode::OpaquePointer ||
209226
!attributedStringBox.getValue().isEmpty()) {

0 commit comments

Comments
 (0)