Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TextInput controlled selection broken on both ios and android. #29063

Open
Gutyn opened this issue Jun 5, 2020 · 26 comments · Fixed by fabOnReact/react-native-improved#16
Open
Labels
Component: TextInput Related to the TextInput component. Issue: Author Provided Repro This issue can be reproduced in Snack or an attached project. Platform: Android Android applications. Platform: iOS iOS applications.

Comments

@Gutyn
Copy link

Gutyn commented Jun 5, 2020

This issue is a continuation of the discussion:
dff490d#commitcomment-39332764
The link to the sample project that demonstrates the issues:
https://github.com/Ginger-Labs/Input-bug

Description

Controlled selection seems to be broken on both ios and android, to demonstrate the issues I created a sample project (find the link above).

React Native version:

System:
OS: macOS 10.15.4
CPU: (8) x64 Intel(R) Core(TM) i7-7820HQ CPU @ 2.90GHz
Memory: 1.55 GB / 16.00 GB
Shell: 5.7.1 - /bin/zsh
Binaries:
Node: 10.14.2 - /usr/local/bin/node
Yarn: 1.13.0 - /usr/local/bin/yarn
npm: 6.14.5 - ~/.npm-global/bin/npm
Watchman: 4.9.0 - /usr/local/bin/watchman
SDKs:
iOS SDK:
Platforms: iOS 13.5, DriverKit 19.0, macOS 10.15, tvOS 13.4, watchOS 6.2
Android SDK:
API Levels: 23, 24, 25, 26, 27, 28, 29
Build Tools: 26.0.3, 28.0.3, 29.0.0, 30.0.0
System Images: android-28 | Google APIs Intel x86 Atom, android-28 | Google Play Intel x86 Atom, android-29 | Google APIs Intel x86 Atom, android-29 | Google Play Intel x86 Atom_64
Android NDK: 21.1.6352462
IDEs:
Android Studio: 3.6 AI-192.7142.36.36.6392135
Xcode: 11.5/11E608c - /usr/bin/xcodebuild
npmPackages:
react: ~16.9.0 => 16.9.0
react-native: ~0.62.2 => 0.62.2
npmGlobalPackages:
create-react-native-app: 3.4.0
react-native-app-id: 0.0.5
react-native-cli: 2.0.1

Steps To Reproduce

The reproduction steps are in the sample project's ReadMe file.
For simplicity purposes, I will post them here as well:

SIM - iPhone 11 (13.4.1):

  1. Click on the "Click Me" Button, set the cursor in the middle, add some text, click on the button again: Notice the text is set to needed one but the selection is not 10
  2. Input text: "Hello world", move cursor in between words, click on "@":
    Expected: "Hello @Mihailworld" with the cursor at 13
    Actual: "Hello @Mihailworld" with the cursor at the end of the whole string.

SIM - nexus 6P API28

  1. Add text, click on enter (new line).
    Expected: The text stays and a new line is created with "-" in front.
    Actual: The first line becomes empty and the second line "-".
  2. Press enter twice and you will get a:
    Exception in native call java.lang.IndexOutOfBoundsException: setSpan (6 ... 6) ends beyond length 3
  3. Press "@" twice and observe the same bug above.

Expected Results

I expect the TextInput to work as intended (unless I am missing something conceptual).

Snack, code example, screenshot, or link to a repository:

https://github.com/Ginger-Labs/Input-bug

@react-native-bot react-native-bot added Component: TextInput Related to the TextInput component. Platform: Android Android applications. Platform: iOS iOS applications. labels Jun 5, 2020
@chrisglein
Copy link

The text string is being programmatically replaced. The expectation stated here is that the cursor is stable as a result of how TextInput.selection is being managed. Relevant bits of code here:

function replace(previous, range, chars) {
  const text = previous.slice(0, range.start) + chars + previous.slice(range.end)
  const sel = range.start + chars.length
  return {
    text,
    selection: {start: sel, end: sel}
  }
}
  onSelectionChange = evt => {
    console.log('onSelectionChange: ', evt.nativeEvent)

    let nextSelection = evt.nativeEvent.selection
    if (!this.inputEvent) {
      return this.setState({selection: nextSelection})
    }

    const {previousText, text, range} = this.inputEvent
    let nextText = replace(this.state.text, range, text).text
    delete this.inputEvent

    function replaceInputWith(chars) {
      const result = replace(previousText, range, chars)
      nextText = result.text
      nextSelection = result.selection
    }

    switch (text) {
      case '@': {
        replaceInputWith('@Mihail')
        break
      }

      case '\n': {
        replaceInputWith('\n- ')
        break
      }
    }

    this.setState({
      text: nextText,
      selection: nextSelection
    })
  }
        <TextInput 
          selection={this.state.selection}
          value={this.state.text}
          placeholder={'Say Something'}
          onSelectionChange={this.onSelectionChange}
          onChange={this.onChange}
          onChangeText={this.onChangeText}
          onTextInput={this.onTextInput}
          onKeyPress={this.onKeyPress}
          multiline
          autoFocus
        />

mikefogg referenced this issue Jul 14, 2020
Summary:
Modernizing this code a bit more, converting it to hooks.

Changelog:
[General][Changed] Converted TextInput to use React hooks
[General][Fixed] TextInput now properly sends native the end selection location on change

(Note: this ignores all push blocking failures!)

Reviewed By: JoshuaGross

Differential Revision: D18581712

fbshipit-source-id: 62d6ea8489fa019ddf941c520930365f2c4887d8
@kelset
Copy link
Contributor

kelset commented Jul 14, 2020

cc @TheSavior I think this is relevant to your commit rewriting TextInput to hooks.

As far as I am aware, there is no available commit on master that would fix it via a cherry pick in a release - but I may be wrong.

@fabOnReact
Copy link
Contributor

fabOnReact commented Jul 14, 2020

My superficial understanding.

The below Java API is called from JavaScript

viewCommands.setTextAndSelection(

case SET_TEXT_AND_SELECTION:
this.receiveCommand(reactEditText, "setTextAndSelection", args);

text can be updated from State, from Javascript, but we also expose separate api to update the Selection using nativeProps

maybeSetText updates the text

public void maybeSetText(ReactTextUpdate reactTextUpdate) {

text is stored in a separate class ReactTextUpdate which includes his own attributes

new SpannableStringBuilder(reactTextUpdate.getText());

The mText state (the string we receive from Javascript) is stored in instance of class ReactTextUpdate attributes mText. This is where it is first set.

new ReactTextUpdate(
spannedFromShadowNode(
this,
getText(),
/* supportsInlineViews: */ false,
/* nativeViewHierarchyOptimizer: */ null // only needed to support inline views
),
mMostRecentEventCount,
mContainsImages,
getPadding(Spacing.LEFT),
getPadding(Spacing.TOP),
getPadding(Spacing.RIGHT),
getPadding(Spacing.BOTTOM),
mTextAlign,
mTextBreakStrategy,
mJustificationMode,
mSelectionStart,
mSelectionEnd);

This is the ReactTextUpdate instance constructor

public ReactTextUpdate(
Spannable text,
int jsEventCounter,
boolean containsImages,
float paddingStart,
float paddingTop,
float paddingEnd,
float paddingBottom,
int textAlign,
int textBreakStrategy,
int justificationMode,
int selectionStart,
int selectionEnd) {
mText = text;

I did some research on this working on #29070 (comment), but I ended up giving up as it was relatively complex functionality.

My main problem when working on #29070 was that length() would be different then actual lenght of the string, causing duplicated letters.

Thanks a lot
I wish you a good day
Fabrizio Bertoglio

@JoshuaGross
Copy link
Contributor

It looks like there could be some relevant commits missing from the 0.63 branch? Forgive me if I'm wrong, maybe some of these are already included but it doesn't look like it to me.

027e8f9
e68f9bf
b861782

There were a lot of changes made to TextInput up through those diffs, roughly, so if some of them aren't included in the branch I would expect some TextInput issues.

@garrettm
Copy link

garrettm commented Jul 15, 2020

Here’s a rough outline of our hack workarounds:

  • never send text/selection via props in render
  • create logic to try to track what you expect the native side to be showing, and if they differ, in render/update (depending on plaintext/attributed) send the text/selection values you want
  • on the JS side coalesce selection + text changes, they need to happen together to have proper logic for changing the string/selection in custom ways
  • when setting selection on Android via setNativeProps, have to re-set it back to null after a 1ms delay, or it'll get stuck
  • various other minor hacks (for example, attributed text needs different logic than plaintext)

It's pretty involved, has a bunch of hacks, and doesn't work all the time. If I was gonna do it again, I'd probably just fork the native code...

If anyone who is working on fixing this bug in RN core would like to chat about my experience with it or possible solutions, I’d love to, anytime.

@fabOnReact
Copy link
Contributor

fabOnReact commented Jul 15, 2020

on Android onTextInput receives

{ 
  nativeEvent: { 
     previousText: ""
  } 
}

when the cursor is moved between the 2 words like this

Before
Large Text[CURSOR]
After
Large [CURSOR]Text

caused from this line. previousText is calculated here with .substring(start, before), but before has value of 0 so substring(6, 6 + 0) returns empty string.

String oldText = mPreviousText.substring(start, start + before);

@Override
public void addTextChangedListener(TextWatcher watcher) {
if (mListeners == null) {
mListeners = new ArrayList<>();
super.addTextChangedListener(getTextWatcherDelegator());
}
mListeners.add(watcher);
}

editText.addTextChangedListener(new ReactTextInputTextWatcher(reactContext, editText));

If I add the below text { start: 0, before: 10}

If I move middle of the word { start: 0, before: 10}

If I insert from the previous cursor position the a character { start: 6, before: 0}

onTextInput receives mPreviousText = empty string

String oldText = mPreviousText.substring(start, start + before);

// Android sends a "onTextChanged" event followed by a "onSelectionChanged" event, for
// the same "most recent event count".
// For controlled selection, that means that immediately after text is updated,
// a controlled component will pass in the *previous* selection, even if the controlled
// component didn't mean to modify the selection at all.
// Therefore, we ignore selections and pass them through until the selection event has
// been sent.
// Note that this mitigation is NOT needed for Fabric.

This issue is not very clear to me especially the example attached.. I unsubscribed from this issue and may not follow-up. Sorry

Additionally:

  1. I click on Click me button, the state text value is set and onTextInput is not called
  2. this.inputEvent is set inside onTextInput callback
  3. additional conditions change the execution of the program.. Are this essential to reproduce the bug?

All this conditions are not relevant for reproducing this bug/issue and they are considerable amount of work for the OpenSource contributor that need to delete existing JavaScript logic to reproduce the issue with a minimal reproducible example

Minimal

The more code there is to go through, the less likely people can find your problem. Streamline your example in one of two ways:

Restart from scratch. Create a new program, adding in only what is needed to see the problem. Use simple, descriptive names for functions and variables – don’t copy the names you’re using in your existing code.
Divide and conquer. If you’re not sure what the source of the problem is, start removing code a bit at a time until the problem disappears – then add the last part back.

Reproducible
Eliminate any issues that aren't relevant to the problem. If your question isn’t about a compiler error, ensure that there are no compile-time errors. Use a program such as JSLint to validate interpreted languages. Validate any HTML or XML.

Sorry, Thanks a lot. Best Regards. Fabrizio Bertoglio

@JoshuaGross
Copy link
Contributor

@garrettm @fabriziobertoglio1987 I would expect a variety of bugs until (at least) the three commits I mentioned above are merged into release.

@garrettm
Copy link

Is it expected that these three commits get into 63.2?

@todorone
Copy link

@TheSavior @kelset controlled selection is broken on both platforms since the component has been rewritten to hooks. Is there anything can be done to fix it as it's critical for some cases?

@kelset
Copy link
Contributor

kelset commented Sep 23, 2020

I thought the commits mentioned by Joshua were cherry picked in 0.63.2. Please submit a new repro if that still happens with latest. We are also working our way towards a 0.63.3 release which should have more fixes.

@focux
Copy link

focux commented Oct 6, 2020

For the record, the controlled selection is still broken in 0.63.3.

@garrettm
Copy link

garrettm commented Oct 6, 2020

We're on 0.63.2 and still having issues too

@velidan
Copy link

velidan commented Dec 9, 2020

Have the same issue, unfortunately. I'd like to wonder if there is any update or suggestion of how to solve it?
Thanks!

@fabOnReact

This comment was marked as outdated.

fabOnReact added a commit to fabOnReact/react-native that referenced this issue Feb 9, 2021
@fabOnReact
Copy link
Contributor

fabOnReact commented Feb 9, 2021

Thanks a lot for the bug report. I tested your example with my pr #29070 and It solves this issue

I would experience the runtime on latest master, but after checking out and building my branch #29070 of react-native from source (see the below test), the error would not reproduce.

I pushed the branch tested below which includes your example in this commit fabOnReact@0660ac9

Please thumbs up my PR, test, feedback is appreciated. Thanks ☮️ ❤️ 🙏

CLICK TO OPEN TESTS RESULTS

BEFORE AFTER

@jocoders
Copy link

I have the same bag on Android on react-native 0.63.2:

Fatal Exception: java.lang.IndexOutOfBoundsException: setSpan (8 ... 8) ends beyond length 7
       at android.text.SpannableStringBuilder.checkRange(SpannableStringBuilder.java:1325)
       at android.text.SpannableStringBuilder.setSpan(SpannableStringBuilder.java:684)
       at android.text.SpannableStringBuilder.setSpan(SpannableStringBuilder.java:676)
       at android.text.Selection.setSelection(Selection.java:94)
       at android.text.Selection.setSelection(Selection.java:78)
       at android.widget.EditText.setSelection(EditText.java:129)
       at com.facebook.react.views.textinput.ReactEditText.setSelection(ReactEditText.java:308)
       at com.facebook.react.views.textinput.ReactEditText.maybeSetSelection(ReactEditText.java:302)
       at com.facebook.react.views.textinput.ReactTextInputManager.updateExtraData(ReactTextInputManager.java:291)
       at com.facebook.react.views.textinput.ReactTextInputManager.updateExtraData(ReactTextInputManager.java:84)
       at com.facebook.react.uimanager.NativeViewHierarchyManager.updateViewExtraData(NativeViewHierarchyManager.java:149)
       at com.facebook.react.uimanager.UIViewOperationQueue$UpdateViewExtraData.execute(UIViewOperationQueue.java:240)
       at com.facebook.react.uimanager.UIViewOperationQueue$1.run(UIViewOperationQueue.java:917)
       at com.facebook.react.uimanager.UIViewOperationQueue.flushPendingBatches(UIViewOperationQueue.java:1028)
       at com.facebook.react.uimanager.UIViewOperationQueue.access$2600(UIViewOperationQueue.java:48)
       at com.facebook.react.uimanager.UIViewOperationQueue$DispatchUIFrameCallback.doFrameGuarded(UIViewOperationQueue.java:1088)
       at com.facebook.react.uimanager.GuardedFrameCallback.doFrame(GuardedFrameCallback.java:29)
       at com.facebook.react.modules.core.ReactChoreographer$ReactChoreographerDispatcher.doFrame(ReactChoreographer.java:175)
       at com.facebook.react.modules.core.ChoreographerCompat$FrameCallback$1.doFrame(ChoreographerCompat.java:85)
       at android.view.Choreographer$CallbackRecord.run(Choreographer.java:965)
       at android.view.Choreographer.doCallbacks(Choreographer.java:791)
       at android.view.Choreographer.doFrame(Choreographer.java:722)
       at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:952)
       at android.os.Handler.handleCallback(Handler.java:883)
       at android.os.Handler.dispatchMessage(Handler.java:100)
       at android.os.Looper.loop(Looper.java:214)
       at android.app.ActivityThread.main(ActivityThread.java:7386)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:514)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:980)

Any ideas how to solve it?

@kyoz
Copy link

kyoz commented Mar 8, 2021

I'm working on an app need add customize words & emoji in Input and after a day got headache with selection, this is my solution. @@ Hope it help or inspire you guy to got another better solution

const inputRef = useRef();
const [text, setText] = useState('');
const [selection, setSelection] = useState({start: 0, end: 0});
const [blockSelection, setBlockSelection] = useState(false);

const addCustomText = (customText) => {
  setBlockSelection(true);

  const newText =
      text.substr(0, selection.start) +
      customText +
      text.substr(selection.end);

  setText(newText);

  // Change The Selection
  const newPos = selection.start + customText.length;

  setSelection({start: newPos, end: newPos});

  inputRef.current.setNativeProps({
    selection: {start: newPos, end: newPos},
  });
};

const handleTextChange = text => {
  setText(text);

  inputRef.current.setNativeProps({
    selection: {
      start: selection.start + 1,
      end: selection.start + 1,
    },
  });
};

const handleSelectionChange = position => {
  if (blockSelection) {
    setBlockSelection(false);
    return;
  }

  setSelection(position);
};
  
  ...
         <TextInput
            ...
            ref={inputRef}
            value={text}
            onChangeText={text => handleTextChange(text)}
            onSelectionChange={e => handleSelectionChange(e.nativeEvent.selection)}
            style={styles.input} />
        </View>

@mir1198yusuf
Copy link

inputRef.current.setNativeProps({ selection: { start: selection.start + 1, end: selection.start + 1, }, });

This works for me but setting selection prop from local state did not work while changing state in onSelectionChange.

Above comments is helpful

@apfritts
Copy link

I think I have another bug related to text input selection #31375. This doesn't seem directly related since it doesn't look like anyone is changing font style though.

@Sanath91009
Copy link

Sanath91009 commented Jul 28, 2022

https://stackoverflow.com/questions/73151416/cursor-not-changing-its-position-in-react-native

If I run the code in android it is working fine but IOS simulator is working differently (working as mentioned in the above question)

update : related to this issue #28865

facebook-github-bot pushed a commit that referenced this issue Mar 1, 2023
Summary:
**Android**: The functionality consists of calling the [AccessibilityNodeInfo#setError][10] and [#setContentInvalid][13] method to display the error message in the TextInput.

**Fixes [https://github.com/facebook/react-native/issues/30848][51] - Adding an accessibilityErrorMessage prop to the TextInput Component**:
**Android**: The prop accessibilityErrorMessage triggers the AccessibilityNodeInfo method [setError][10] which automatically sets the correct properties on the AccessibilityNodeInfo that will inform screen readers of this state. The method calls setContentInvalid(true) and setError(youErrorString) on the AccessibilityNodeInfo.

**Fixes [https://github.com/facebook/react-native/issues/30859][52] -  Detecting changes in the Error state (text inputs)**
**Fabric - Android** - Adding accessibilityErrorMessage to field AndroidTextInputState.
ReactTextInputManager and ReactEditText receive state updates both from [Javascript][32] and [cpp (fabric)][34].
- accessibilityErrorMessage is added to the fabric AndroidTextInputState field
- The updates are received in the ReactAndroid API with method updateState from ReactTextInputManager
- After updating the TextInput text with onChangeText, the update of the accessibilityErrorMessage is triggered with method maybeSetAccessibilityError which triggers [setError][10].

More info:
- An explanation of [state updates between fabric and ReactAndroid for the TextInput component][34]
- [ReactNative renderer state updates][35]

**Paper - Android** - Adding accessibilityErrorMessage to ReactTextInputShadowNode to trigger updates in Paper renderer when accessibilityErrorMessage is changed within the onChange callback.

Related Links (Android):
- [In this diff I'm shipping and deleting mapBufferSerialization for Text measurement][101]
- [This diff implement and integrates Mapbuffer into Fabric text measure system][39]
- [Refactor ViewPropsMapBuffer -> general MapBuffer props mechanism][100]
- [TextInput: support modifying TextInputs with multiple Fragments (Cxx side)][24]
- [TextInput: keep C++ state in-sync with updated AttributedStrings in Java][23]
- [AccessibilityNodeInfo#setError][11]
- [Explanation on how TextInput calls SET_TEXT_AND_SELECTION in Java API][32]
- [Fabric: convertRawProp was extended to accept an optional default value][27]
- [understanding onChangeText callback][31]
- [Editable method replace()][12]
- [Change of error state from onChangeText show/hides a TextInput error][30]
- [AndroidTextInput: support using commands instead of setNativeProps (native change)][25]
- [TextInput: support editing completely empty TextInputs][26]
- [[Android] Fix letters duplication when using autoCapitalize https://github.com/facebook/react-native/issues/29070][40]
- [Support optional types for C++ TurboModules][28]
- [discussion on using announceForAccessibility in ReactEditText][36]
- [ fix annoucement delayed to next character][61]
- [Announce accessibility state changes happening in the background][29]
- [Refactor MountingManager into MountingManager + SurfaceMountingManager][37]

iOS Functionalities are included in separate PR #35908
Documentation PR facebook/react-native-website#3010

Next PR [2/2 TextInput accessibilityErrorMessage (VoiceOver, iOS) https://github.com/facebook/react-native/issues/35908](https://github.com/facebook/react-native/pull/35908)
Related facebook/react-native-deprecated-modules#18

## Changelog

[Android] [Added] - Adding TextInput prop accessibilityErrorMessage to announce with TalkBack screenreaders

Pull Request resolved: #33468

Test Plan:
**Android - 20 Jan 2023**
#33468 (comment)

**iOS - 20 Jan 2023**
#33468 (comment)

<details><summary>CLICK TO OPEN OLD VIDEO TEST CASES</summary>
<p>

**PR Branch - Android and iOS 24th June**
[88]: Android - accessibilityValue announces correctly with/out errorMessage set with onChangeText or with outside event (Fabric) ([link][88])

**PR Branch - Android**
[1]. Test Cases of the functionality (Fabric) ([link][1])
[2]. Test Cases of the functionality (Paper) ([link][2])

**Main Branch**
[6]. Android - Runtime Error in main branch when passing value of 1 to TextInput  placeholder prop ([link][6])

**Issues Solved**
[7]. TalkBack error does not clear error on the next typed character when using onChangeText ([link][7])
**Other Tests**
[8]. Setting the TextInput errorMessage state with setTextAndSelection Java API from JavaScript ([link][8])
[9]. Setting the TextInput errorMessage state from fabric TextInput internal state to Java ReactTextUpdate API ([link][9])

</p>
</details>

[1]: fabOnReact/react-native-notes#12 (comment) "Test Cases of the functionality (Android - Fabric)"
[2]: fabOnReact/react-native-notes#12 (comment) "Test Cases of the functionality (Android - Paper)"
[3]: fabOnReact/react-native-notes#12 (comment) "Test Cases of the functionality (iOS - Fabric)"
[6]: fabOnReact/react-native-notes#12 (comment) "Runtime Error in main branch when passing value of 1 to TextInput  placeholder prop"
[7]: fabOnReact/react-native-notes#12 (comment) "TalkBack error announcement done on next typed character with onChangeText"
[8]: fabOnReact/react-native-notes#12 (comment) "setting the TextInput errorMessage state with setTextAndSelection Java API from JavaScript"
[9]: fabOnReact/react-native-notes#12 (comment) "Setting the TextInput errorMessage state from fabric TextInput internal state to Java ReactTextUpdate API"

[10]: https://developer.android.com/reference/android/view/accessibility/AccessibilityNodeInfo#setError(java.lang.CharSequence) "AOSP setError"
[11]: https://developer.android.com/reference/android/view/accessibility/AccessibilityNodeInfo#setError(java.lang.CharSequence) "AccessibilityNodeInfo#setError"
[12]: https://github.com/aosp-mirror/platform_frameworks_base/blob/1ac46f932ef88a8f96d652580d8105e361ffc842/core/java/android/text/Editable.java#L28-L52 "Editable method replace"
[13]: https://developer.android.com/reference/android/view/accessibility/AccessibilityNodeInfo#setContentInvalid(boolean) "setContentInvalid"

[20]: 60b6c9b "draft implementation of android_errorMessage "
[21]: 012d92d "add errorMessage to ReactTextUpdate and maybeSetAccessibilityError"
[22]: fabOnReact@cad239b "rename android_errorMessage to errorMessageAndroid"
[23]: fabOnReact@0bae474 "TextInput: keep C++ state in-sync with updated AttributedStrings in Java"
[24]: fabOnReact@0556e86 "TextInput: support modifying TextInputs with multiple Fragments (Cxx side)"
[25]: fabOnReact@7ab5eb4 "AndroidTextInput: support using commands instead of setNativeProps (native change)"
[26]: fabOnReact@b9491b7 "TextInput: support editing completely empty TextInputs"
[27]: fabOnReact@7f1ed68 "Fabric: convertRawProp was extended to accept an optional default value"
[28]: 6e0fa5f "Support optional types for C++ TurboModules"
[29]: fabOnReact@baa66f6 "Announce accessibility state changes happening in the background"

[30]: fabOnReact/react-native-notes#12 (comment) "Change of error state from onChangeText show/hides a TextInput error"
[31]: fabOnReact/react-native-notes#12 (comment) "understanding onChangeText callback"
[32]: #29063 (comment) "Explanation on how TextInput calls SET_TEXT_AND_SELECTION in Java API"
[33]: #33468 (comment) "Explanation of TextInput state management with fabric C++ and JAVA API"
[34]: #33468 (comment) "state updates between fabric and ReactAndroid for the TextInput component"
[35]: https://reactnative.dev/architecture/render-pipeline#react-native-renderer-state-updates "ReactNative renderer state updates"
[35]: fabOnReact/react-native-notes#12 (comment) "Analysis on how AndroidTextInputState.cpp sends updates to ReactTextInputManager"
[36]: #33468 (comment) "discussion on using announceForAccessibility in ReactEditText"
[37]: fabOnReact@29eb632 "Refactor MountingManager into MountingManager + SurfaceMountingManager"
[38]: fabOnReact@733f228 "Diff C++ props for Android consumption"
[39]: fabOnReact@91b3f5d "This diff implement and integrates Mapbuffer into Fabric text measure system"

[40]: #29070 "[Android] Fix letters duplication when using autoCapitalize #29070"

[50]: fabOnReact/react-native-notes#12  "Notes from work on iOS/Android: Text input error for screenreaders #12"
[51]: #30848 "iOS/Android: Text input error for screenreaders #30848"
[52]: #30859 "Android: Error state change (text inputs) #30859"

[61]: eb33c93 "fix annoucement delayed to next character"

[70]: fabOnReact/react-native-notes#12 (comment) "iOS - Paper renderer does not update the accessibilityValue"
[71]: fabOnReact/react-native-notes#12 (comment) "Test Cases of the functionality (Fabric) after removing changes to .cpp libs"
[72]: fabOnReact/react-native-notes#12 (comment) "Test Cases of the functionality (Paper) after removing changes to .cpp libs"
[73]: fabOnReact/react-native-notes#12 (comment) "iOS - announcing error onChangeText and screenreader focus"
[74]: fabOnReact/react-native-notes#12 (comment) "iOS - The screenreader announces the TextInput value after the errorMessage is cleared"
[75]: fabOnReact/react-native-notes#12 (comment) "iOS - Exception thrown while executing UI block: - [RCTTextView setOnAccessibiltyAction:]: unrecognized selector sent to instance (Paper) (main branch)"
[76]: #30859 (comment) "iOS - announce lastChar (not entire text) onChangeText and avoid multiple announcements (Fabric)"
[77]: #30859 (comment) "iOS - announces or does not announce the accessibilityError through Button onPress (not onChangeText) (Fabric)"
[78]: #30859 (comment) "iOS - the error is announced with accessibilityInvalid true and does not clear after typing text (onChangeText) (Fabric)"
[79]: #30848 (comment) "iOS - Exception thrown while executing UI block: - RCTUITextView setAccessibilityErrorMessage:]: unrecognized selector sent to instance (iOS - Paper on main branch)"

[80]: fabOnReact@e13b9c6 "RCTTextField was spliited into two classes"
[81]: fabOnReact@ee9697e "Introducing RCTBackedTextInputDelegate"
[82]: fabOnReact@2dd2529 "Add option to hide context menu for TextInput"
[83]: https://github.com/fabriziobertoglio1987/react-native/blob/343eea1e3150cf54d6f7727cd01d13eb7247c7f7/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentAccessibilityProvider.mm#L48-L72 "RCTParagraphComponentAccessibilityProvider accessibilityElements"
[84]: https://github.com/fabriziobertoglio1987/react-native/blob/c8790a114f6f21774c43f0e9b9210e7b35d1c243/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm#L613 "RCTTextInputComponentView method _setAttributedString"
[85]: https://github.com/fabriziobertoglio1987/react-native/blob/c8790a114f6f21774c43f0e9b9210e7b35d1c243/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm#L146 "RCTTextInputComponentView method updateProps"
[86]: https://github.com/fabriziobertoglio1987/react-native/blob/c8790a114f6f21774c43f0e9b9210e7b35d1c243/Libraries/Text/TextInput/RCTBaseTextInputView.m#L150 "RCTBaseTextInputView setAttributedText"
[87]: #30859 (comment) "iOS - accessibilityValue announces correctly with/out errorMessage set with onChangeText or with outside event"
[88]: #30859 (comment) "Android - accessibilityValue announces correctly with/out errorMessage set with onChangeText or with outside event"
[89]: #30859 (comment) "iOS - accessibilityValue announces correctly with/out errorMessage set with onChangeText or with outside event (Fabric)"

[100]: fabOnReact@110b191 "Refactor ViewPropsMapBuffer -> general MapBuffer props mechanism"
[101]: fabOnReact@22b6e1c "In this diff I'm shipping and deleting mapBufferSerialization for Text measurement"

Reviewed By: blavalla

Differential Revision: D38410635

Pulled By: lunaleaps

fbshipit-source-id: cd80e9a1be8f5ca017c979d7907974cf72ca4777
@ZComwiz
Copy link

ZComwiz commented Mar 2, 2023

We are getting errors on Samsung devices for SpannableText:

[SpannableStringBuilder.java line 1123](https://notifications.googleapis.com/email/...someLongURL)
android.text.SpannableStringBuilder.siftDown

running "react-native": "0.71.1",

The issue appears to effect Samsung Phones more and when users change to using GBoard Keyboard, the error sometimes goes away? Super janky. Its a new issue in Android 13

OlimpiaZurek pushed a commit to OlimpiaZurek/react-native that referenced this issue May 22, 2023
…#33468)

Summary:
**Android**: The functionality consists of calling the [AccessibilityNodeInfo#setError][10] and [#setContentInvalid][13] method to display the error message in the TextInput.

**Fixes [https://github.com/facebook/react-native/issues/30848][51] - Adding an accessibilityErrorMessage prop to the TextInput Component**:
**Android**: The prop accessibilityErrorMessage triggers the AccessibilityNodeInfo method [setError][10] which automatically sets the correct properties on the AccessibilityNodeInfo that will inform screen readers of this state. The method calls setContentInvalid(true) and setError(youErrorString) on the AccessibilityNodeInfo.

**Fixes [https://github.com/facebook/react-native/issues/30859][52] -  Detecting changes in the Error state (text inputs)**
**Fabric - Android** - Adding accessibilityErrorMessage to field AndroidTextInputState.
ReactTextInputManager and ReactEditText receive state updates both from [Javascript][32] and [cpp (fabric)][34].
- accessibilityErrorMessage is added to the fabric AndroidTextInputState field
- The updates are received in the ReactAndroid API with method updateState from ReactTextInputManager
- After updating the TextInput text with onChangeText, the update of the accessibilityErrorMessage is triggered with method maybeSetAccessibilityError which triggers [setError][10].

More info:
- An explanation of [state updates between fabric and ReactAndroid for the TextInput component][34]
- [ReactNative renderer state updates][35]

**Paper - Android** - Adding accessibilityErrorMessage to ReactTextInputShadowNode to trigger updates in Paper renderer when accessibilityErrorMessage is changed within the onChange callback.

Related Links (Android):
- [In this diff I'm shipping and deleting mapBufferSerialization for Text measurement][101]
- [This diff implement and integrates Mapbuffer into Fabric text measure system][39]
- [Refactor ViewPropsMapBuffer -> general MapBuffer props mechanism][100]
- [TextInput: support modifying TextInputs with multiple Fragments (Cxx side)][24]
- [TextInput: keep C++ state in-sync with updated AttributedStrings in Java][23]
- [AccessibilityNodeInfo#setError][11]
- [Explanation on how TextInput calls SET_TEXT_AND_SELECTION in Java API][32]
- [Fabric: convertRawProp was extended to accept an optional default value][27]
- [understanding onChangeText callback][31]
- [Editable method replace()][12]
- [Change of error state from onChangeText show/hides a TextInput error][30]
- [AndroidTextInput: support using commands instead of setNativeProps (native change)][25]
- [TextInput: support editing completely empty TextInputs][26]
- [[Android] Fix letters duplication when using autoCapitalize https://github.com/facebook/react-native/issues/29070][40]
- [Support optional types for C++ TurboModules][28]
- [discussion on using announceForAccessibility in ReactEditText][36]
- [ fix annoucement delayed to next character][61]
- [Announce accessibility state changes happening in the background][29]
- [Refactor MountingManager into MountingManager + SurfaceMountingManager][37]

iOS Functionalities are included in separate PR facebook#35908
Documentation PR facebook/react-native-website#3010

Next PR [2/2 TextInput accessibilityErrorMessage (VoiceOver, iOS) https://github.com/facebook/react-native/issues/35908](https://github.com/facebook/react-native/pull/35908)
Related facebook/react-native-deprecated-modules#18

## Changelog

[Android] [Added] - Adding TextInput prop accessibilityErrorMessage to announce with TalkBack screenreaders

Pull Request resolved: facebook#33468

Test Plan:
**Android - 20 Jan 2023**
facebook#33468 (comment)

**iOS - 20 Jan 2023**
facebook#33468 (comment)

<details><summary>CLICK TO OPEN OLD VIDEO TEST CASES</summary>
<p>

**PR Branch - Android and iOS 24th June**
[88]: Android - accessibilityValue announces correctly with/out errorMessage set with onChangeText or with outside event (Fabric) ([link][88])

**PR Branch - Android**
[1]. Test Cases of the functionality (Fabric) ([link][1])
[2]. Test Cases of the functionality (Paper) ([link][2])

**Main Branch**
[6]. Android - Runtime Error in main branch when passing value of 1 to TextInput  placeholder prop ([link][6])

**Issues Solved**
[7]. TalkBack error does not clear error on the next typed character when using onChangeText ([link][7])
**Other Tests**
[8]. Setting the TextInput errorMessage state with setTextAndSelection Java API from JavaScript ([link][8])
[9]. Setting the TextInput errorMessage state from fabric TextInput internal state to Java ReactTextUpdate API ([link][9])

</p>
</details>

[1]: fabOnReact/react-native-notes#12 (comment) "Test Cases of the functionality (Android - Fabric)"
[2]: fabOnReact/react-native-notes#12 (comment) "Test Cases of the functionality (Android - Paper)"
[3]: fabOnReact/react-native-notes#12 (comment) "Test Cases of the functionality (iOS - Fabric)"
[6]: fabOnReact/react-native-notes#12 (comment) "Runtime Error in main branch when passing value of 1 to TextInput  placeholder prop"
[7]: fabOnReact/react-native-notes#12 (comment) "TalkBack error announcement done on next typed character with onChangeText"
[8]: fabOnReact/react-native-notes#12 (comment) "setting the TextInput errorMessage state with setTextAndSelection Java API from JavaScript"
[9]: fabOnReact/react-native-notes#12 (comment) "Setting the TextInput errorMessage state from fabric TextInput internal state to Java ReactTextUpdate API"

[10]: https://developer.android.com/reference/android/view/accessibility/AccessibilityNodeInfo#setError(java.lang.CharSequence) "AOSP setError"
[11]: https://developer.android.com/reference/android/view/accessibility/AccessibilityNodeInfo#setError(java.lang.CharSequence) "AccessibilityNodeInfo#setError"
[12]: https://github.com/aosp-mirror/platform_frameworks_base/blob/1ac46f932ef88a8f96d652580d8105e361ffc842/core/java/android/text/Editable.java#L28-L52 "Editable method replace"
[13]: https://developer.android.com/reference/android/view/accessibility/AccessibilityNodeInfo#setContentInvalid(boolean) "setContentInvalid"

[20]: facebook@60b6c9b "draft implementation of android_errorMessage "
[21]: facebook@012d92d "add errorMessage to ReactTextUpdate and maybeSetAccessibilityError"
[22]: fabOnReact@cad239b "rename android_errorMessage to errorMessageAndroid"
[23]: fabOnReact@0bae474 "TextInput: keep C++ state in-sync with updated AttributedStrings in Java"
[24]: fabOnReact@0556e86 "TextInput: support modifying TextInputs with multiple Fragments (Cxx side)"
[25]: fabOnReact@7ab5eb4 "AndroidTextInput: support using commands instead of setNativeProps (native change)"
[26]: fabOnReact@b9491b7 "TextInput: support editing completely empty TextInputs"
[27]: fabOnReact@7f1ed68 "Fabric: convertRawProp was extended to accept an optional default value"
[28]: facebook@6e0fa5f "Support optional types for C++ TurboModules"
[29]: fabOnReact@baa66f6 "Announce accessibility state changes happening in the background"

[30]: fabOnReact/react-native-notes#12 (comment) "Change of error state from onChangeText show/hides a TextInput error"
[31]: fabOnReact/react-native-notes#12 (comment) "understanding onChangeText callback"
[32]: facebook#29063 (comment) "Explanation on how TextInput calls SET_TEXT_AND_SELECTION in Java API"
[33]: facebook#33468 (comment) "Explanation of TextInput state management with fabric C++ and JAVA API"
[34]: facebook#33468 (comment) "state updates between fabric and ReactAndroid for the TextInput component"
[35]: https://reactnative.dev/architecture/render-pipeline#react-native-renderer-state-updates "ReactNative renderer state updates"
[35]: fabOnReact/react-native-notes#12 (comment) "Analysis on how AndroidTextInputState.cpp sends updates to ReactTextInputManager"
[36]: facebook#33468 (comment) "discussion on using announceForAccessibility in ReactEditText"
[37]: fabOnReact@29eb632 "Refactor MountingManager into MountingManager + SurfaceMountingManager"
[38]: fabOnReact@733f228 "Diff C++ props for Android consumption"
[39]: fabOnReact@91b3f5d "This diff implement and integrates Mapbuffer into Fabric text measure system"

[40]: facebook#29070 "[Android] Fix letters duplication when using autoCapitalize facebook#29070"

[50]: fabOnReact/react-native-notes#12  "Notes from work on iOS/Android: Text input error for screenreaders facebook#12"
[51]: facebook#30848 "iOS/Android: Text input error for screenreaders facebook#30848"
[52]: facebook#30859 "Android: Error state change (text inputs) facebook#30859"

[61]: facebook@eb33c93 "fix annoucement delayed to next character"

[70]: fabOnReact/react-native-notes#12 (comment) "iOS - Paper renderer does not update the accessibilityValue"
[71]: fabOnReact/react-native-notes#12 (comment) "Test Cases of the functionality (Fabric) after removing changes to .cpp libs"
[72]: fabOnReact/react-native-notes#12 (comment) "Test Cases of the functionality (Paper) after removing changes to .cpp libs"
[73]: fabOnReact/react-native-notes#12 (comment) "iOS - announcing error onChangeText and screenreader focus"
[74]: fabOnReact/react-native-notes#12 (comment) "iOS - The screenreader announces the TextInput value after the errorMessage is cleared"
[75]: fabOnReact/react-native-notes#12 (comment) "iOS - Exception thrown while executing UI block: - [RCTTextView setOnAccessibiltyAction:]: unrecognized selector sent to instance (Paper) (main branch)"
[76]: facebook#30859 (comment) "iOS - announce lastChar (not entire text) onChangeText and avoid multiple announcements (Fabric)"
[77]: facebook#30859 (comment) "iOS - announces or does not announce the accessibilityError through Button onPress (not onChangeText) (Fabric)"
[78]: facebook#30859 (comment) "iOS - the error is announced with accessibilityInvalid true and does not clear after typing text (onChangeText) (Fabric)"
[79]: facebook#30848 (comment) "iOS - Exception thrown while executing UI block: - RCTUITextView setAccessibilityErrorMessage:]: unrecognized selector sent to instance (iOS - Paper on main branch)"

[80]: fabOnReact@e13b9c6 "RCTTextField was spliited into two classes"
[81]: fabOnReact@ee9697e "Introducing RCTBackedTextInputDelegate"
[82]: fabOnReact@2dd2529 "Add option to hide context menu for TextInput"
[83]: https://github.com/fabriziobertoglio1987/react-native/blob/343eea1e3150cf54d6f7727cd01d13eb7247c7f7/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentAccessibilityProvider.mm#L48-L72 "RCTParagraphComponentAccessibilityProvider accessibilityElements"
[84]: https://github.com/fabriziobertoglio1987/react-native/blob/c8790a114f6f21774c43f0e9b9210e7b35d1c243/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm#L613 "RCTTextInputComponentView method _setAttributedString"
[85]: https://github.com/fabriziobertoglio1987/react-native/blob/c8790a114f6f21774c43f0e9b9210e7b35d1c243/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm#L146 "RCTTextInputComponentView method updateProps"
[86]: https://github.com/fabriziobertoglio1987/react-native/blob/c8790a114f6f21774c43f0e9b9210e7b35d1c243/Libraries/Text/TextInput/RCTBaseTextInputView.m#L150 "RCTBaseTextInputView setAttributedText"
[87]: facebook#30859 (comment) "iOS - accessibilityValue announces correctly with/out errorMessage set with onChangeText or with outside event"
[88]: facebook#30859 (comment) "Android - accessibilityValue announces correctly with/out errorMessage set with onChangeText or with outside event"
[89]: facebook#30859 (comment) "iOS - accessibilityValue announces correctly with/out errorMessage set with onChangeText or with outside event (Fabric)"

[100]: fabOnReact@110b191 "Refactor ViewPropsMapBuffer -> general MapBuffer props mechanism"
[101]: fabOnReact@22b6e1c "In this diff I'm shipping and deleting mapBufferSerialization for Text measurement"

Reviewed By: blavalla

Differential Revision: D38410635

Pulled By: lunaleaps

fbshipit-source-id: cd80e9a1be8f5ca017c979d7907974cf72ca4777
@joemun
Copy link

joemun commented May 27, 2023

Saw that the React Native team recently merged in this PR updating controlled selection logic for Android:
#37424

@kyoz
Copy link

kyoz commented Jun 26, 2023

I'm working on an app need add customize words & emoji in Input and after a day got headache with selection, this is my solution. @@ Hope it help or inspire you guy to got another better solution

const inputRef = useRef();
const [text, setText] = useState('');
const [selection, setSelection] = useState({start: 0, end: 0});
const [blockSelection, setBlockSelection] = useState(false);

const addCustomText = (customText) => {
  setBlockSelection(true);

  const newText =
      text.substr(0, selection.start) +
      customText +
      text.substr(selection.end);

  setText(newText);

  // Change The Selection
  const newPos = selection.start + customText.length;

  setSelection({start: newPos, end: newPos});

  inputRef.current.setNativeProps({
    selection: {start: newPos, end: newPos},
  });
};

const handleTextChange = text => {
  setText(text);

  inputRef.current.setNativeProps({
    selection: {
      start: selection.start + 1,
      end: selection.start + 1,
    },
  });
};

const handleSelectionChange = position => {
  if (blockSelection) {
    setBlockSelection(false);
    return;
  }

  setSelection(position);
};
  
  ...
         <TextInput
            ...
            ref={inputRef}
            value={text}
            onChangeText={text => handleTextChange(text)}
            onSelectionChange={e => handleSelectionChange(e.nativeEvent.selection)}
            style={styles.input} />
        </View>

This is no longer work in RN 0.72.0. I still looking for solution, tried setSelection but it seem doesn't work

@kyoz
Copy link

kyoz commented Jun 26, 2023

Since my older solution doesn't work. Cause we can't set selection with setNativeProps anymore in RN 0.72. I just update my apps and find a new solution.

In RN 0.72. You can use value and selection properties to handle selection and input value. Like so:

const [inputValue, setInputValue] = useState('');
const [inputSelection, setInputSelection] = useState({start: 0, end: 0});
const [blockSelection, setBlockSelection] = useState(false);

const addCustomText = (customText) => {
  setBlockSelection(true);

  const newText =
      text.substr(0, selection.start) +
      customText +
      text.substr(selection.end);

  // Change The Selection
  const newPos = selection.start + customText.length;
  
  setInputValue(newText);
  setInputSelection({start: newPos, end: newPos});
};

const handleTextChange = text => {
  setInputValue(text);
  setInputSelection({
      start: selection.start + 1,
      end: selection.start + 1,
   });
};

const handleSelectionChange = position => {
  if (blockSelection) {
    setBlockSelection(false);
    return;
  }

  setInputSelection(position);
};
  
  ...
         <TextInput
            ...
            value={inputValue}
            selection={inputSelection}
            onChangeText={text => handleTextChange(text)}
            onSelectionChange={e => handleSelectionChange(e.nativeEvent.selection)}
            ... />
        </View>

But be aware that when using States. It will re-render the whole component. So put your input to isolate component or find your own solutions to keep good performance for the input.

Have tested on Android ans iOS. It's seem the selection properties work fine and accurate.

Hope it help.

@fabOnReact
Copy link
Contributor

Do you still experience this issue?

I have four years of experience maintaining facebook/react-native and I specialize in the Text and TextInput components. I currently have 58 facebook/react-native PRs.

If you still experience this issue, I will prepare a patched release with the fix.

Thanks a lot

@fabOnReact
Copy link
Contributor

This PR is included in the react-native-improved library:

react-native-improved

  • Supports ONLY react-native 0.73.
  • Supports only old architechture (new architechture is WIP)

Set-up

In package.json

 "scripts": {
+  "postinstall": "yarn run react-native-patch"
 }

Then

npm

npm install react-native-improved --save-dev

yarn v1

yarn add react-native-improved --dev

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Component: TextInput Related to the TextInput component. Issue: Author Provided Repro This issue can be reproduced in Snack or an attached project. Platform: Android Android applications. Platform: iOS iOS applications.
Projects
None yet