Expensify / react-native-live-markdown

Drop-in replacement for React Native's TextInput component with Markdown formatting.

Home Page:https://www.npmjs.com/package/@expensify/react-native-live-markdown

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

`onSelectionChange` event called twice with different payload on iOS for multiline

tomekzaw opened this issue · comments

Reported by @perunt at https://expensify.slack.com/archives/C01GTK53T8Q/p1707846163324679

Reproducible on 5375a10 + iOS + multiline (Android works correctly, singleline works correctly)

Screen.Recording.2024-02-14.at.14.54.10.mov
App.tsx
import * as React from 'react';

import {StyleSheet, View} from 'react-native';

import {MarkdownTextInput} from '@expensify/react-native-live-markdown';

export default function App() {
  const [value, setValue] = React.useState('');

  return (
    <View style={styles.container}>
      <MarkdownTextInput
        multiline
        autoCapitalize="none"
        value={value}
        onChangeText={setValue}
        style={styles.input}
        onSelectionChange={(e) => console.log(e.nativeEvent)}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    marginTop: 60,
  },
  input: {
    fontSize: 20,
    width: 300,
    padding: 5,
    borderColor: 'gray',
    borderWidth: 1,
    textAlignVertical: 'top',
  },
});

Root cause analysis

onSelectionChange events are emitted in [RCTBaseTextInputView textInputDidChangeSelection] by calling _onSelectionChange function.

When typing at the end of the text input, it works correctly (only single event is emitted).

When typing in the middle of the contents, two events are emitted.

The first event is emitted as a result of setAttributedText: call inside [RCTBaseTextInputView(Markdown) markdown_updateLocalData]:

first

However, the payload is incorrect since the selection is automatically set to the end of the current text:

first_selection

The second event is emitted as a result of calling setSelectedTextRange: to restore the original cursor position:

second

It has a correct payload:

second_selection

The solution would be to somehow prevent emitting the first event.