diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index 8c532d85502b..10eda031ccf1 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -482,8 +482,7 @@ - (void)setTextAndSelection:(NSInteger)eventCount if (value && ![value isEqualToString:_backedTextInputView.attributedText.string]) { NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:value attributes:_backedTextInputView.defaultTextAttributes]; - [self _setAttributedString:attributedString]; - [self _updateState]; + [self _updateStateWithString:attributedString]; } UITextPosition *startPosition = [_backedTextInputView positionFromPosition:_backedTextInputView.beginningOfDocument @@ -493,6 +492,7 @@ - (void)setTextAndSelection:(NSInteger)eventCount if (startPosition && endPosition) { UITextRange *range = [_backedTextInputView textRangeFromPosition:startPosition toPosition:endPosition]; + // _updateStateWithString executes any state updates sync, so its safe to update the selection as the attributedString is already updated! [_backedTextInputView setSelectedTextRange:range notifyDelegate:NO]; } _comingFromJS = NO; @@ -614,17 +614,27 @@ - (void)handleInputAccessoryDoneButton } - (void)_updateState +{ + NSAttributedString *attributedString = _backedTextInputView.attributedText; + [self _updateStateWithString:attributedString]; +} + +- (void)_updateStateWithString:(NSAttributedString*)attributedString { if (!_state) { return; } - NSAttributedString *attributedString = _backedTextInputView.attributedText; auto data = _state->getData(); _lastStringStateWasUpdatedWith = attributedString; data.attributedStringBox = RCTAttributedStringBoxFromNSAttributedString(attributedString); _mostRecentEventCount += _comingFromJS ? 0 : 1; data.mostRecentEventCount = _mostRecentEventCount; - _state->updateState(std::move(data)); + const auto &textInputEventEmitter = static_cast(*_eventEmitter); + // When the textInputDidChange gets called, the text is already updated + // in the UI. We execute the state update synchronously so that the layout gets calculated immediately. + textInputEventEmitter.experimental_flushSync([state = _state, data = std::move(data)]() mutable { + state->updateState(std::move(data)); + }); } - (AttributedString::Range)_selectionRange