facebook / react-native

A framework for building native applications using React
https://reactnative.dev
MIT License
118.17k stars 24.21k forks source link

Can't modify text in TextInput onChangeText callback on Android #23578

Open rogerbright opened 5 years ago

rogerbright commented 5 years ago

πŸ› Bug Report

On Android, modifying the text within the onChange (or onChangeText) callback causes corruption of the text in the TextInput. (Not tested on iOS.)

For example, I'm trying to force all caps in my TextInput field. (This is to work around the react native autoCapitalize issue described here: https://github.com/facebook/react-native/issues/8932). So if a lowercase letter is entered, I change it to uppercase in the callback. Unfortunately, alternate keystrokes cause the entire previous text to be duplicated, but only if the entered keystroke was lowercase.

So, when forcing all caps, entering 1234 results in 1234 showing up; entering ABCD results in ABCD showing up; but entering abcd results in AABCAABCD.

This issue disappears if assigning a Math.random() key to the TextInput; but then of course so does the keyboard focus, making this an unacceptable workaround.

To Reproduce

See "Bug Report" and "Code Example" sections.

Expected Behavior

One should be able to modify the value inside TextInput's change callbacks, without the text becoming corrupted on the subsequent redisplay.

Code Example

export default class TestScr extends Component
{
  constructor(props)
  {
    super(props);
    this.state = { s6: '' };
  }
  textchg(event)
  {
    const {eventCount, target, text} = event.nativeEvent;
            // one would expect the contents of s6 to display after the redraw
    this.setState({ s6: text.toUpperCase() }); 
  }
  render()
  {
            // [same behavior if using onChangeText instead of onChange]
    let jsx0 = <View style={{ flexDirection: 'row' }} key={ 'hi' }>
        <TextInput placeholder={ 'hello' } value={ this.state.s6 }
            onChange={ (evt) => this.textchg(evt) }
            keyboardType={ 'default' } />
        </View>;

    return (<View style={{ backgroundColor: '#ffffff', padding: 10, }}>
        <ScrollView style={{ backgroundColor: '#ffffff', }}>
            { jsx0 }
        </ScrollView>
    </View>);
  }
}

Environment

React Native Environment Info: System: OS: Linux 3.19 Ubuntu 14.04.3 LTS, Trusty Tahr CPU: (4) x64 Intel(R) Core(TM) i7-5500U CPU @ 2.40GHz Memory: 626.14 MB / 15.38 GB Shell: 6.18.01 - /bin/tcsh Binaries: Node: 8.11.3 - /usr/bin/node npm: 5.6.0 - /usr/bin/npm SDKs: Android SDK: API Levels: 10, 16, 23, 26, 27, 28 Build Tools: 19.1.0, 20.0.0, 21.1.2, 22.0.1, 23.0.1, 23.0.2, 26.0.3, 27.0.3, 28.0.2, 28.0.3 System Images: android-16 | ARM EABI v7a, android-23 | Intel x86 Atom_64, android-23 | Google APIs Intel x86 Atom_64, android-28 | Google APIs Intel x86 Atom npmPackages: react: 16.6.3 => 16.6.3 react-native: 0.58.6 => 0.58.6 npmGlobalPackages: create-react-native-app: 1.0.0 react-native-cli: 2.0.1

mathbalduino commented 1 year ago

Hey guys, any news on this one?

danielcrk commented 1 year ago

I guess not.

hieucd04 commented 1 year ago

+1

chancegraff commented 1 year ago

I'm seeing this on iOS but I suspect it's because of one of the packages I have installed.

  "dependencies": {
    "@react-navigation/bottom-tabs": "^6.5.3",
    "@react-navigation/native": "^6.1.2",
    "@react-navigation/native-stack": "^6.9.8",
    "expo": "^47.0.13",
    "expo-splash-screen": "~0.17.5",
    "expo-status-bar": "^1.4.2",
    "firebase": "^9.15.0",
    "immer": "^9.0.17",
    "jest": "^29.3.1",
    "react": "18.1.0",
    "react-dom": "18.1.0",
    "react-native": "0.70.5",
    "react-native-dotenv": "^3.4.7",
    "react-native-paper": "^5.1.3",
    "react-native-plaid-link-sdk": "^8.0.1",
    "react-native-safe-area-context": "^4.4.1",
    "react-native-screens": "^3.18.2",
    "react-native-text-input-mask": "^3.1.4",
    "react-native-web": "^0.18.10",
    "zustand": "^4.3.2"
  },
  "devDependencies": {
    "@babel/core": "^7.20.12",
    "@types/react": "^18.0.26",
    "@types/react-native": "^0.71.0",
    "babel-plugin-module-resolver": "^5.0.0",
    "babel-plugin-root-import": "^6.6.0",
    "expo-cli": "^6.1.0",
    "typescript": "^4.9.4"
  },
saschagehlich commented 1 year ago

@chancegraff I have the same issue on iOS, were you able to fix this?

jcubic commented 1 year ago

The same issue on Android in Emulator. It works fine for the Expo playground so don't use it to test.

Simple reproduction:

const ControlledComponent = () => {
  const [state, setState] = useState('0.00');
  const cursor = 1;
  return (
    <TextInput
      ref={ref}
      autoFocus
      onKeyPress={() => { /* empty */ }}
      selection={{start: cursor, end: cursor}}
      value={state}
      keyboardType="numeric"
    />
  );
};

There is no way to prevent default behavior, event.preventDefault() also doesn't work. The input is always updated even if the component is controlled.

cihangir-mercan commented 1 year ago

The issue still persists on both IOS and Android

I try to limit decimal points to 8 length. The user sees 9th character shortly before removal. It causes flickery.

https://snack.expo.dev/N5IcDRFWc

fabOnReact commented 8 months ago

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

DanielO199 commented 7 months ago

Yes, this bug still occurs. Please take care of it

yuki384 commented 7 months ago

@fabOnReact Yes, the issue still persists (on iOS). If you could tackle this, it would be incredibly appreciated!

mkhoussid commented 7 months ago

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

Kind of a hacky work around (and doesn't always work quite as expected):

const onChange = (e: string) => {
    if (isValidInput(e)) {
        textInputRef.current?.setNativeProps({ text: e });
    }
}
yuki384 commented 7 months ago

Kind of a hacky work around (and doesn't always work quite as expected):

const onChange = (e: string) => {
    if (isValidInput(e)) {
        textInputRef.current?.setNativeProps({ text: e });
    }
}

Thank you! I already tried that, but It's still flickering on device... It works fine on Simulator. Incidentally, I would like to prevent type new lines.

mkhoussid commented 7 months ago

Kind of a hacky work around (and doesn't always work quite as expected):

const onChange = (e: string) => {
    if (isValidInput(e)) {
        textInputRef.current?.setNativeProps({ text: e });
    }
}

Thank you! I already tried that, but It's still flickering on device... It works fine on Simulator. Incidentally, I would like to prevent type new lines.

Can you try something like this?

. . .
const previousValueRef = React.useRef('');
. . .
const onChange = (e: string) => {
    if (textInputRef.current) {
        const textInput = textInputRef.current;
        textInput.setNativeProps({ text: previousValueRef.current });
        if (isValidInput(e)) {
            textInput.setNativeProps({ text: e });
            previousValueRef.current = e;
        }
    }
}
. . .

If you need to accept only integers and know your max length, there was a suggestion from this SO post:

<TextInput
  maxLength={MAX_LENGTH}
  keyboardType="numeric"
  value={value}
  onChangeText={(e) => {
    if (/^\d+$/.test(e)) setValue(e);
  }}
  placeholder="Type only numbers..."
/>
fabOnReact commented 7 months ago

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

react-native-improved

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
yuki384 commented 7 months ago

Can you try something like this?

Thank you! This did not work out well either. However, it might have been due to me including a Text component as a child of TextInput to finely control the style :

<TextInput ... >
  <Text>Some</Text>
  <Text style={{ ... }}>String</Text>
</TextInput>

For my case, I've decided to use a custom Native Module, but I'll give your solution a try another time.

react-native-bot commented 1 month ago

This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.

jcubic commented 1 month ago

A PR with a fix was created a year ago, I don't know what else I can do.

cortinico commented 1 month ago

A PR with a fix was created a year ago, I don't know what else I can do.

What's the PR?

jcubic commented 1 month ago

It was linked:

https://github.com/facebook/react-native/issues/38155

cortinico commented 1 month ago

It was linked:

38155

That's an issue, not a PR

jcubic commented 1 month ago

That's an issue, not a PR

Ah sorry my mistake, I thought it was the issue where I created a PR for.