Repository navigation
Conversation
Summary: Add a new RNTester example in TextInputSharedExamples to reproduce iOS and Android issues with auto-expanding multiline text fields. There is already an existing example, but an empty, uncontrolled variant is needed to reproduce certain issues. Changelog: [Internal] Differential Revision: D127048669
Summary: Fixes an issue where on iOS, multiline expanding TextInputs do not expand if the input starts off as empty and uncontrolled. ### Root Cause When a multiline TextInput on iOS is provided a `value` and `onChange()` prop, as shown in the [React Native docs](https://reactnative.dev/docs/textinput), the input grows as expected. However, uncontrolled inputs do not grow past the first initial line. On mount, `BaseTextInputShadowNode::updateStateIfNeeded()` writes state only if the text changes, so the first state write is skipped for empty, uncontrolled inputs. As a result, `TextAttributes` keeps the default `fontSizeMultiplier = NaN` which is carried over since iOS copies the previous state for subsequent keystrokes. During measurement, `attributedStringBoxToMeasure()` sees that the current multiplier doesn't match the stored multiplier of NaN and falls back to `props.text` which is empty for uncontrolled inputs. This causes the input to stay one-line tall. ### This PR Modify `BaseTextInputShadowNode::updateStateIfNeeded()` to update the state if base text attributes differ. On mount, the default font multiplier of NaN now differs from the real multiplier, so a state update occurs and the text field grows normally. Changelog: [iOS][Fixed] - Fix multiline TextInput not growing with its content when uncontrolled and initially empty Differential Revision: D127048714
Summary: Fixes the issue described in [react#46813](react#46813), where multiline TextInputs on Android experience a content jump when adding and removing lines that change the input's height. ### Root Cause ReactEditText hardcodes `isLayoutRequested() = false` which causes Android to immediately scroll the caret to the newly typed character instead of waiting until the next layout. When adding a new line to an auto-expanding input, Android scrolls to the new line before Fabric's asynchronous state update triggers a new layout. This manifests as a glitchy content jump since the current line briefly scrolls to the newly added line before being reset by the incoming layout. In React Native, text is centered vertically in TextInputs by default. When removing a line from an auto-expanding input, Android immediately recenters the text in the remaining space before Fabric's asynchronous state update occurs, causing another glitchy content jump since the current line briefly shifts from its current position due to recentering before being reset by the incoming layout. One workaround for this is to set the `textAlignVertical` CSS style in the opposite direction of the growth direction, such as `textAlignVertical: 'top'` for a text field that grows downwards. ### This PR Update `ReactTextInputTextWatcher` to trigger an `unstable_Immediate` synchronous state update when a multiline text field's line count changes. This ensures that a layout arrives in the next frame, avoiding the existing race issues where Android scrolls or recenters before the next layout. Gated behind `fixMultilineContentJumpAndroid`. ### Limitations This fix depends on synchronous state updates, which could have performance concerns. This is mitigated by limiting synchronous updates only to when the input expands or shrinks, with all other updates remaining async. One potential alternative is to use scroll deferral to fix the content jump when adding new lines. By conditionally setting `isLayoutRequested() = true` in ReactEditText, the content jump can be avoided by delaying the scroll in cases when we can guarantee a layout is pending. However, this means ReactEditText needs to predict when Fabric guarantees a layout, which means hardcoding assumptions about Fabric measurement that break if the measurement code changes. Alternatively, ReactEditText would need a working `requestLayout()`, but this goes against existing architectural patterns (see `ReactViewGroup.requestLayout()`) where UIManager is meant to handle laying out children. Even then, would only fix the content jump when adding new lines and not when removing lines. Changelog: [Android]Fixed] - Fix content jump for expanding multiline TextInputs on Android Differential Revision: D127048758
|
@jasozh has exported this pull request. If you are a Meta employee, you can view the originating Diff in D127048758. |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary:
Fixes the issue described in #46813, where multiline TextInputs on Android experience a content jump when adding and removing lines that change the input's height.
Root Cause
ReactEditText hardcodes
isLayoutRequested() = falsewhich causes Android to immediately scroll the caret to the newly typed character instead of waiting until the next layout. When adding a new line to an auto-expanding input, Android scrolls to the new line before Fabric's asynchronous state update triggers a new layout. This manifests as a glitchy content jump since the current line briefly scrolls to the newly added line before being reset by the incoming layout.In React Native, text is centered vertically in TextInputs by default. When removing a line from an auto-expanding input, Android immediately recenters the text in the remaining space before Fabric's asynchronous state update occurs, causing another glitchy content jump since the current line briefly shifts from its current position due to recentering before being reset by the incoming layout. One workaround for this is to set the
textAlignVerticalCSS style in the opposite direction of the growth direction, such astextAlignVertical: 'top'for a text field that grows downwards.This PR
Update
ReactTextInputTextWatcherto trigger anunstable_Immediatesynchronous state update when a multiline text field's line count changes. This ensures that a layout arrives in the next frame, avoiding the existing race issues where Android scrolls or recenters before the next layout.Gated behind
fixMultilineContentJumpAndroid.Limitations
This fix depends on synchronous state updates, which could have performance concerns. This is mitigated by limiting synchronous updates only to when the input expands or shrinks, with all other updates remaining async.
One potential alternative is to use scroll deferral to fix the content jump when adding new lines. By conditionally setting
isLayoutRequested() = truein ReactEditText, the content jump can be avoided by delaying the scroll in cases when we can guarantee a layout is pending. However, this means ReactEditText needs to predict when Fabric guarantees a layout, which means hardcoding assumptions about Fabric measurement that break if the measurement code changes. Alternatively, ReactEditText would need a workingrequestLayout(), but this goes against existing architectural patterns (seeReactViewGroup.requestLayout()) where UIManager is meant to handle laying out children. Even then, would only fix the content jump when adding new lines and not when removing lines.Changelog: [Android]Fixed] - Fix content jump for expanding multiline TextInputs on Android
Differential Revision: D127048758