Environment:
OS: macOS High Sierra 10.13.2
Node: 8.9.1
Yarn: 1.9.4
npm: 5.8.0
Watchman: 4.7.0
Xcode: Xcode 9.2 Build version 9C40b
Android Studio: 3.1 AI-173.4907809
Packages: (wanted => installed)
react: 16.5.0 => 16.5.0
react-native: 0.57.0 => 0.57.0
On IOS, adding emojis to TextInput causes the following:
Works in RN0.53.
Started breaking in RN0.54 and still breaks in RN0.57.
RN0.57 has a bug that breaks the RN build. To test on RN0.57, apply this manual fix when the build fails the first time: https://github.com/facebook/react-native/issues/21071#issuecomment-420648890
See snack here:
https://snack.expo.io/@superandrew213/textinput-emoji-bug
Test on device and not on appetize/browser.

https://github.com/facebook/react-native/issues/19389, https://github.com/facebook/react-native/issues/20908, https://github.com/wix/react-native-autogrow-textinput/issues/47
It looks like you are using an older version of React Native. Please update to the latest release, v0.57 and verify if the issue still exists.
The ":rewind:Old Version" label will be removed automatically once you edit your original post with the results of running react-native info on a project using the latest release.
@shergin could you have a look at this? I can see that you did the reimplementation of TextInput in RN0.54.
All emoji characters are automatically given an AppleColorEmoji NSFont attribute and the original font is moved to NSOriginalFont attribute.
This causes an issue when we call isEqualToAttributedString when we set the attributedText, since it will always be false if an emoji is present.
See here: https://stackoverflow.com/a/39456258
I guess we might need to add an exception for emojis here too and compare strings only and leave out attributes:
Or remove the attributes that are automatically added before we do the comparison.
This may be related to #20908 ; still happening in 57.1
This is my patch. @dchersey feel free to create a PR if you are keen.
Patch is based on RN56. In RN57 method textOf was added. You just need to modify a small part of it to account for emojis.
patch-package
--- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m
+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m
@@ -98,10 +98,40 @@ - (NSAttributedString *)attributedText
return self.backedTextInputView.attributedText;
}
+- (BOOL)textOf:(NSAttributedString*)newText equals:(NSAttributedString*)oldText{
+ // When the dictation is running we can't update the attibuted text on the backed up text view
+ // because setting the attributed string will kill the dictation. This means that we can't impose
+ // the settings on a dictation.
+ // Similarly, when the user is in the middle of inputting some text in Japanese/Chinese, there will be styling on the
+ // text that we should disregard. See https://developer.apple.com/documentation/uikit/uitextinput/1614489-markedtextrange?language=objc
+ // for more info.
+ // Also if user has added an emoji, the sytem adds a font attribute for the emoji and stores the original font in NSOriginalFont.
+ // Lastly, when entering a password, etc., there will be additional styling on the field as the native text view
+ // handles showing the last character for a split second.
+ __block BOOL fontHasBeenUpdatedBySystem = false;
+ [oldText enumerateAttribute:@"NSOriginalFont" inRange:NSMakeRange(0, oldText.length) options:0 usingBlock:^(id value, NSRange range, BOOL *stop) {
+ if (value){
+ fontHasBeenUpdatedBySystem = true;
+ }
+ }];
+
+ BOOL shouldFallbackToBareTextComparison =
+ [self.backedTextInputView.textInputMode.primaryLanguage isEqualToString:@"dictation"] ||
+ self.backedTextInputView.markedTextRange ||
+ self.backedTextInputView.isSecureTextEntry ||
+ fontHasBeenUpdatedBySystem;
+
+ if (shouldFallbackToBareTextComparison) {
+ return ([newText.string isEqualToString:oldText.string]);
+ } else {
+ return ([newText isEqualToAttributedString:oldText]);
+ }
+}
+
- (void)setAttributedText:(NSAttributedString *)attributedText
{
NSInteger eventLag = _nativeEventCount - _mostRecentEventCount;
-
+ BOOL textNeedsUpdate = NO;
// Remove tag attribute to ensure correct attributed string comparison.
NSMutableAttributedString *const backedTextInputViewTextCopy = [self.backedTextInputView.attributedText mutableCopy];
NSMutableAttributedString *const attributedTextCopy = [attributedText mutableCopy];
@@ -112,7 +142,9 @@ - (void)setAttributedText:(NSAttributedString *)attributedText
[attributedTextCopy removeAttribute:RCTTextAttributesTagAttributeName
range:NSMakeRange(0, attributedTextCopy.length)];
- if (eventLag == 0 && ![attributedTextCopy isEqualToAttributedString:backedTextInputViewTextCopy]) {
+ textNeedsUpdate = ([self textOf:attributedTextCopy equals:backedTextInputViewTextCopy] == NO);
+
+ if (eventLag == 0 && textNeedsUpdate) {
UITextRange *selection = self.backedTextInputView.selectedTextRange;
NSInteger oldTextLength = self.backedTextInputView.attributedText.string.length;
Thanks, I will test it out tomorrow morning! To clarify, is this expected to address #20908 as well as this issue?
@dchersey both. The issue is the same.
That did the trick! PR will follow shortly.
Here's the tested patch updated for 0.57.x in case anyone needs it ... use patch_package to apply it during npm or yarn install.
patch-package
--- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m
+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m
@@ -105,12 +105,22 @@ - (BOOL)textOf:(NSAttributedString*)newText equals:(NSAttributedString*)oldText{
// Similarly, when the user is in the middle of inputting some text in Japanese/Chinese, there will be styling on the
// text that we should disregard. See https://developer.apple.com/documentation/uikit/uitextinput/1614489-markedtextrange?language=objc
// for more info.
+ // Also if user has added an emoji, the sytem adds a font attribute for the emoji and stores the original font in NSOriginalFont.
// Lastly, when entering a password, etc., there will be additional styling on the field as the native text view
// handles showing the last character for a split second.
+ __block BOOL fontHasBeenUpdatedBySystem = false;
+ [oldText enumerateAttribute:@"NSOriginalFont" inRange:NSMakeRange(0, oldText.length) options:0 usingBlock:^(id value, NSRange range, BOOL *stop) {
+ if (value){
+ fontHasBeenUpdatedBySystem = true;
+ }
+ }];
+
BOOL shouldFallbackToBareTextComparison =
[self.backedTextInputView.textInputMode.primaryLanguage isEqualToString:@"dictation"] ||
self.backedTextInputView.markedTextRange ||
- self.backedTextInputView.isSecureTextEntry;
+ self.backedTextInputView.isSecureTextEntry ||
+ fontHasBeenUpdatedBySystem;
+
if (shouldFallbackToBareTextComparison) {
return ([newText.string isEqualToString:oldText.string]);
} else {
Most helpful comment
Here's the tested patch updated for 0.57.x in case anyone needs it ... use patch_package to apply it during npm or yarn install.