Skip to content

Commit

Permalink
Switch order of onSelectionChange and onChange events send from native
Browse files Browse the repository at this point in the history
Summary:
Changelog: [Internal]

UIKit uses either `UITextField` or `UITextView` as its UIKit element for `<TextInput>`. `UITextField` is for single line entry, `UITextView` is for multiline entry.

There is a problem with order of events when user types a character.

In `UITextField` (single line text entry), typing a character first triggers `onChange` event and then `onSelectionChange`. JavaScript depends on this order of events because it uses `mostRecentEventCount` from this even to communicate to native that it is in sync with changes in native.

In `UITextView` (multi line text entry), typing a character first triggers `onSelectionChange` and then `onChange`. As JS depends on the correct order of events, this can cause issues. An example would be a TextInput which changes contents based as a result of `onSelectionChange`. Those changes would be ignored as native will throw them away because JavaScript doesn't have the newest version.

Reviewed By: JoshuaGross

Differential Revision: D20836195

fbshipit-source-id: fbae3b6c0d388fc059ca2541ae980073b8e5f6c7
  • Loading branch information
sammy-SC authored and facebook-github-bot committed Apr 9, 2020
1 parent 4a48b02 commit 7b48899
Showing 1 changed file with 27 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@ @implementation RCTTextInputComponentView {
TextInputShadowNode::ConcreteState::Shared _state;
UIView<RCTBackedTextInputViewProtocol> *_backedTextInputView;
size_t _stateRevision;
NSAttributedString *_lastStringStateWasUpdatedWith;

/*
* UIKit uses either UITextField or UITextView as its UIKit element for <TextInput>. UITextField is for single line
* entry, UITextView is for multiline entry. There is a problem with order of events when user types a character. In
* UITextField (single line text entry), typing a character first triggers `onChange` event and then
* onSelectionChange. In UITextView (multi line text entry), typing a character first triggers `onSelectionChange` and
* then onChange. JavaScript depends on `onChange` to be called before `onSelectionChange`. This flag keeps state so
* if UITextView is backing text input view, inside `-[RCTTextInputComponentView textInputDidChangeSelection]` we make
* sure to call `onChange` before `onSelectionChange` and ignore next `-[RCTTextInputComponentView
* textInputDidChange]` call.
*/
BOOL _ignoreNextTextInputCall;
}

- (instancetype)initWithFrame:(CGRect)frame
Expand All @@ -41,6 +54,7 @@ - (instancetype)initWithFrame:(CGRect)frame
_backedTextInputView = props.traits.multiline ? [[RCTUITextView alloc] init] : [[RCTUITextField alloc] init];
_backedTextInputView.frame = self.bounds;
_backedTextInputView.textInputDelegate = self;
_ignoreNextTextInputCall = NO;
_stateRevision = State::initialRevisionValue;
[self addSubview:_backedTextInputView];
}
Expand Down Expand Up @@ -190,6 +204,8 @@ - (void)prepareForRecycle
_backedTextInputView.attributedText = [[NSAttributedString alloc] init];
_state.reset();
_stateRevision = State::initialRevisionValue;
_lastStringStateWasUpdatedWith = nil;
_ignoreNextTextInputCall = NO;
}

#pragma mark - RCTComponentViewProtocol
Expand Down Expand Up @@ -294,6 +310,10 @@ - (BOOL)textInputShouldChangeTextInRange:(NSRange)range replacementText:(NSStrin

- (void)textInputDidChange
{
if (_ignoreNextTextInputCall) {
_ignoreNextTextInputCall = NO;
return;
}
[self _updateState];

if (_eventEmitter) {
Expand All @@ -303,6 +323,12 @@ - (void)textInputDidChange

- (void)textInputDidChangeSelection
{
auto const &props = *std::static_pointer_cast<TextInputProps const>(_props);
if (props.traits.multiline && ![_lastStringStateWasUpdatedWith isEqual:_backedTextInputView.attributedText]) {
[self textInputDidChange];
_ignoreNextTextInputCall = YES;
}

if (_eventEmitter) {
std::static_pointer_cast<TextInputEventEmitter const>(_eventEmitter)->onSelectionChange([self _textInputMetrics]);
}
Expand All @@ -327,6 +353,7 @@ - (void)_updateState
}

auto data = _state->getData();
_lastStringStateWasUpdatedWith = attributedString;
data.attributedStringBox = RCTAttributedStringBoxFromNSAttributedString(attributedString);
_state->updateState(std::move(data), EventPriority::SynchronousUnbatched);
_stateRevision = _state->getRevision() + 1;
Expand Down

0 comments on commit 7b48899

Please sign in to comment.