From d88dd145074ee84de0cc4207de02a3cd79c9ceff Mon Sep 17 00:00:00 2001 From: "Deepanshu.shukla" Date: Wed, 31 Jul 2024 04:06:14 -0700 Subject: [PATCH] fix: onChangeText should be called only once if changed via js in a multiline textinput (#45401) Summary: Fixes [44566](https://github.com/facebook/react-native/issues/44566) Issue was onChangeText was called 5-6 times if maxLength was set in a multiline component and TextInput Value was changed via state update. `if (_maxLength) { NSInteger allowedLength = MAX( _maxLength.integerValue - (NSInteger)backedTextInputView.attributedText.string.length + (NSInteger)range.length, 0); if (text.length > allowedLength) { // If we typed/pasted more than one character, limit the text inputted. if (text.length > 1) { if (allowedLength > 0) { // make sure unicode characters that are longer than 16 bits (such as emojis) are not cut off NSRange cutOffCharacterRange = [text rangeOfComposedCharacterSequenceAtIndex:allowedLength - 1]; if (cutOffCharacterRange.location + cutOffCharacterRange.length > allowedLength) { // the character at the length limit takes more than 16bits, truncation should end at the character before allowedLength = cutOffCharacterRange.location; } } // Truncate the input string so the result is exactly maxLength NSString *limitedString = [text substringToIndex:allowedLength]; NSMutableAttributedString *newAttributedText = [backedTextInputView.attributedText mutableCopy]; // Apply text attributes if original input view doesn't have text. if (backedTextInputView.attributedText.length == 0) { newAttributedText = [[NSMutableAttributedString alloc] initWithString:[self.textAttributes applyTextAttributesToText:limitedString] attributes:self.textAttributes.effectiveTextAttributes]; } else { [newAttributedText replaceCharactersInRange:range withString:limitedString]; } backedTextInputView.attributedText = newAttributedText; _predictedText = newAttributedText.string; // Collapse selection at end of insert to match normal paste behavior. UITextPosition *insertEnd = [backedTextInputView positionFromPosition:backedTextInputView.beginningOfDocument offset:(range.location + allowedLength)]; [backedTextInputView setSelectedTextRange:[backedTextInputView textRangeFromPosition:insertEnd toPosition:insertEnd] notifyDelegate:YES]; [self textInputDidChange]; } return nil; // Rejecting the change. }}` This is the original code snippet. It was happening because of wrong check of maxLength with text length `if (text.length > allowedLength)` this should be `(text.length > _maxLength.integerValue)` and `if (allowedLength <= 0)` we should not change the string and fire `textInputDidChange` ## Changelog: [IOS] [FIXED] : Fixing maxLength check which was firing onChange multiple times For more details, see: https://reactnative.dev/contributing/changelogs-in-pull-requests Pull Request resolved: https://github.com/facebook/react-native/pull/45401 Test Plan: Tested in Ios Ran yarn test Screenshot 2024-07-12 at 1 00 28 PM Reviewed By: sammy-SC Differential Revision: D59911745 Pulled By: cipolleschi fbshipit-source-id: 67410ec50d6a2415e568e1685699bfed02fd0a27 --- .../Libraries/Text/TextInput/RCTBaseTextInputView.mm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm index 72cf885c37e..787985edd9b 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm +++ b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm @@ -448,7 +448,7 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame : (CGRect)frame) _maxLength.integerValue - (NSInteger)backedTextInputView.attributedText.string.length + (NSInteger)range.length, 0); - if (text.length > allowedLength) { + if (text.length > _maxLength.integerValue) { // If we typed/pasted more than one character, limit the text inputted. if (text.length > 1) { if (allowedLength > 0) { @@ -459,6 +459,9 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame : (CGRect)frame) allowedLength = cutOffCharacterRange.location; } } + if (allowedLength <= 0) { + return nil; + } // Truncate the input string so the result is exactly maxLength NSString *limitedString = [text substringToIndex:allowedLength]; NSMutableAttributedString *newAttributedText = [backedTextInputView.attributedText mutableCopy];