react-native: TextInput controlled selection broken on both ios and android.

This issue is a continuation of the discussion: https://github.com/facebook/react-native/commit/dff490d140010913d3209a2f3e987914b9c4eee4#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® Core™ 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

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 11
  • Comments: 26 (8 by maintainers)

Commits related to this issue

Most upvoted comments

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>

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.

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.

https://github.com/facebook/react-native/commit/027e8f9b1600c931aa4b826b905a67969733c6ea https://github.com/facebook/react-native/commit/e68f9bf76846dbbc767ec4dbcabc9d83adb5f0dd https://github.com/facebook/react-native/commit/b861782562080bf9c91afcac0f823d96088c2d8d

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.

Is it expected that these three commits get into 63.2?

Thanks a lot for the bug report. I tested your example with my pr https://github.com/facebook/react-native/pull/29070 and It solves this issue

I would experience the runtime on latest master, but after checking out and building my branch https://github.com/facebook/react-native/pull/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 https://github.com/fabriziobertoglio1987/react-native/commit/0660ac910da342574c2b7e6c9e8f5790f71312ec

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

CLICK TO OPEN TESTS RESULTS

BEFORE AFTER

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

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

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

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

I think it requires a big change in both ReactAndroid and ReactNative JS libraries in fact I gave up and search for a new issue to solve https://github.com/facebook/react-native/issues/30393#issuecomment-752676908

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

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.