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

Controlled TextInput broken for Chinese (and other languages) in v0.54 on iOS #18403

Closed
danielsuo opened this issue Mar 16, 2018 · 39 comments
Closed
Labels
Component: TextInput Related to the TextInput component. Impact: Regression Describes a behavior that used to work on a prior release, but stopped working recently. Issue: Author Provided Repro This issue can be reproduced in Snack or an attached project. Platform: iOS iOS applications. Ran Commands One of our bots successfully processed a command. Resolution: Fixed A PR that fixes this issue has been merged. Resolution: Locked This issue was locked by the bot.

Comments

@danielsuo
Copy link

danielsuo commented Mar 16, 2018

Controlled TextInput breaks the Chinese pinyin keyboard's autocomplete feature. A similar issue was raised a year and a half ago (#8265) and fixed by #7496. However, it appears that others are having the same problem (#12599, #18260, #18379), but re-filing the issue with the template filled out and an example to reproduce.

Note that this works correctly in v0.53. This may have something to do with the big Text, TextInput refactor that dropped with v0.54 (Thanks, btw, @shergin and @hovox!).

I've included both a working (v0.53) and broken (v0.54) example below.

Environment

Environment:
OS: macOS High Sierra 10.13
Node: 9.3.0
Yarn: 1.3.2
npm: 5.6.0
Watchman: 4.9.0
Xcode: Xcode 9.2 Build version 9C40b
Android Studio: Not Found

Packages: (wanted => installed)
react: 16.2.0 => 16.2.0
react-native: 0.54.0 => 0.54.0

Expected Behavior

Typing on a US keyboard would bring up Chinese characters corresponding to letters typed.

Actual Behavior

Each new letter typed is considered individually, instead of along with the previous untranslated characters.

Shamelessly stealing an image from @Foru who did a nice job of showing the issue:

image

Steps to Reproduce

Unfortunately, expo hasn't updated to the latest version of React Native. I've prepared a small project that demonstrates the issue here. There is a working version on a branch here; same project, just using RN v0.53 instead of v0.54.

@danielsuo danielsuo changed the title Controlled TextInput broken for Chinese (and other languages) in v0.54 Controlled TextInput broken for Chinese (and other languages) in v0.54 on iOS Mar 16, 2018
@react-native-bot react-native-bot added Platform: iOS iOS applications. Ran Commands One of our bots successfully processed a command. labels Mar 16, 2018
@hramos hramos added Core Team Issue: Author Provided Repro This issue can be reproduced in Snack or an attached project. Impact: Regression Describes a behavior that used to work on a prior release, but stopped working recently. labels Mar 16, 2018
@hramos
Copy link
Contributor

hramos commented Mar 16, 2018

Great bug report! Thanks for organizing all of the reports.

@hramos hramos added the Component: TextInput Related to the TextInput component. label Mar 16, 2018
@react-native-bot react-native-bot added the Missing Test Plan This PR appears to be missing a test plan. label Mar 16, 2018
@hramos hramos removed the Missing Test Plan This PR appears to be missing a test plan. label Mar 16, 2018
@magicien
Copy link
Contributor

@danielsuo I created a PR #18456 to fix this issue.
I made sure that it works for Japanese, but I'm not sure about Chinese.
I forked your test app and applied this change.
Can you (or anyone who can speak Chinese) try this app to check if it's fixed?

@llr101
Copy link

llr101 commented Mar 20, 2018

if it has prop value or defaultvalue,you can't input Chinese

@magicien
Copy link
Contributor

Thank you @booyeu! It might need to include #18278.
Let me update the PR.

@magicien
Copy link
Contributor

I updated the pull request and the sample app.
And I tried one of Chinese keyboards. I hope it's expected behavior.
screenshot

@llr101
Copy link

llr101 commented Mar 20, 2018

I changed and tried again , but it doesn't work . Do you have the prop "defaultValue" like defaultValue=this.props.value ? I found it ok that using defaultValue="test"

@magicien
Copy link
Contributor

I tried 2 cases, and both of them seemed working.

<TextInput
  placeholder="Type here!"
  onChangeText={text => {
    this.setState({ text: text });
  }}
>
  <Text>{this.state.text}</Text>
</TextInput>
<TextInput
  placeholder="Type here!"
  onChangeText={text => {
    this.setState({ text: text });
  }}
  value={this.state.text}
/>

@magicien
Copy link
Contributor

I tried receiving defaultValue as prop, and it also seemed working.

const TestComponent = ({ value }) => (
  <TextInput
    placeholder="Type here!"
    defaultValue={value}
  />
);

export default class App extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <View style={styles.container}>
        <TestComponent value="Default Text" />
      </View>
    );
  }
}

test2

@llr101
Copy link

llr101 commented Mar 20, 2018

Oh My fault . I appreciate your help !!! Thanks !!

@magicien
Copy link
Contributor

All right. Thank you!

@danielsuo
Copy link
Author

Works for me! Appreciate the quick fix!

@react-native-bot react-native-bot added the Platform: macOS Building on macOS. label Mar 20, 2018
@magicien
Copy link
Contributor

Thank you @danielsuo!
I'm going to update the test plan of the pull request.

@Rob117
Copy link

Rob117 commented Apr 5, 2018

This wasn't merged into 0.54.4 right?
Is this in 0.55.0?

@feng-zhang0712
Copy link

0.55.0 has this problem

@mpyw
Copy link

mpyw commented May 31, 2018

Currently the following comment provides the best solution

#18456 (comment)

@tianqiwuben
Copy link

Has this been fixed in 0.56?

@vovkasm
Copy link
Contributor

vovkasm commented Jun 11, 2018

@Ben5276 I will test it shortly... but probably not, because I tracked commits to master, and did not see anything that looks like fix for TextInput :-/
Update: I confused this bug with #18874, so sorry, see comment below.

@vovkasm
Copy link
Contributor

vovkasm commented Jun 11, 2018

I checked it, at first glance Japanese input seems to work. But I using it rarely, so can't be totally sure.
My test code is here: /~https://github.com/vovkasm/react-native-textinput-bug/tree/rn-0.56 (it is for another bug, so the application tries to filter input, but while filtering is broken, anyone can test anything :-)

@ghost
Copy link

ghost commented Jun 22, 2018

@tsangwailam
Thank you. Here is HOC version

withHandleHandWritingTextInput.js

import React, { Component } from 'react';
import {
  Platform,
} from 'react-native';

// /~https://github.com/facebook/react-native/issues/18403
const withHandleHandWritingTextInput = (WrappedComponent) => {
  class HandleHandWritingTextInput extends React.PureComponent {
    render() {
      const { onChangeText, onBlur, ...rest } = this.props;

      return <WrappedComponent
        onChangeText={text => {
          this.tempText = text;
        }}
        onBlur={e => {
          if (onChangeText) {
            onChangeText(this.tempText);
          }
          if (onBlur) {
            onBlur(e);
          }
        }}
        {...rest}
      />;
    }
  }

  return HandleHandWritingTextInput;
}

export default withHandleHandWritingTextInput;

And in another js use by

import {withHandleHandWritingTextInput} from 'withHandleHandWritingTextInput.js';

const MyTextInput = withHandleHandWritingTextInput(TextInput);

@dogecrypto
Copy link

Not solved yet? Temporally hack code maybe not good to tackle on...

@tarouboy
Copy link

tarouboy commented Jul 11, 2018

I was facing the same issue.
After different tests. It seems to be fine if TextInput with onChangeText but not setting the value props.

<TextInput onChangeText={ text => this.setState({ inputText: text }) } />

And this.state.inputText returns exactly the same as what I can see from the screen.
Any potential problems if using TextInput without setting the value props?

@vovkasm
Copy link
Contributor

vovkasm commented Jul 11, 2018

@kevinkevw, but most of times you need to set value. Without a value, how you would implement "reset" functionality, or set initial value?
And exactly without a value, you simple do not trigger this bug, because your TextInput now effectively uncontrolled.

@tarouboy
Copy link

@vovkasm Ah yes you're right. I got your point. But the bug itself still not yet been fixed.

Leaving it uncontrolled seems working fine in my case, I set the initial value by defaultValue props and I don't really have to reset the TextInput, maybe forcing a re-render? : /

@kk412027247
Copy link

@ tsangwailam 唔该晒!!

@kk412027247
Copy link

kk412027247 commented Jul 17, 2018

未命名.gif

@tsangwailam In order to be compatible with android, I change some code.
The following code work well for me.

import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View, TextInput} from 'react-native';


export default class App extends Component {

  state= {
    text:"", // hold the final text
    text2:"", // temporary store the input text
  };

  handleInput = (text) =>  this.setState({ text2: text });
  handleDisplay = (text) =>  this.setState({text:text});

  render() {
    return (
      <View style={styles.container}>
        <TextInput
          value={this.state.text}
          placeholder={'type here'}
          onChangeText={Platform.OS ==='ios' ? this.handleInput : this.handleDisplay}
          onBlur={ Platform.OS ==='ios' ? this.handleDisplay.bind(null,this.state.text2) : null}
        />
        <Text style={styles.text}>{this.state.text}</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  input:{
    width:'100%'
  },
  text: {
    textAlign: 'center',
    margin: 20,
    color:'#999'
  },
});

@mmmulani
Copy link
Contributor

I just landed a fix for this on iOS. Please try it out and let me know if it doesn't work for this problem.

@hramos hramos added the Resolution: Fixed A PR that fixes this issue has been merged. label Jul 30, 2018
MarkRunWu pushed a commit to MarkRunWu/react-native that referenced this issue Jul 31, 2018
Summary:
@public
This should fix facebook#18403.
When the user is inputting in Chinese/Japanese with <TextInput> in a controlled manner, the RCTBaseTextInputView will compare the JS-generated attributed string against the TextInputView attributed string and repeatedly overwrite the TextInputView one. This is because the native TextInputView will provide extra styling to show that some text is provisional.
My solution is to do a plain text string comparison at this point, like how we do for dictation.

Expected behavior when typing in a language that has "multistage" text input: For instance, in Chinese/Japanese it's common to type out the pronunciation for a word and then choose the appropriate word from above the keyboard. In this model, the "pronunciation" shows up in the text box first and then is replaced with the chosen word.
Using the word Japan which is written 日本 but first typed as にほん. It takes 4 key-presses to get to 日本, since に, ほ, ん, are all typed and then 日本 is selected. So here is what should happen:

1. enter に, onChange fires with 'に', markedTextRange covers 'に'
2. enter ほ, onChange fires with 'にほ', markedTextRange covers 'にほ'
3. enter ん, onChange fires with 'にほん', markedTextRange covers 'にほん'
4. user selects 日本 from the menu above the keyboard (provided by the keyboard/OS), onChange fires with '日本', markedTextRange is removed

previously we were overwriting the attributed text which would remove the markedTextRange, preventing the user from selecting 日本 from above the keyboard.

Cheekily, I've also fixed an issue with secure text entry as it's the same type of problem.

Reviewed By: PeteTheHeat

Differential Revision: D9002295

fbshipit-source-id: 7304ede055f301dab9ce1ea70f65308f2a4b4a8f
sidnair pushed a commit to taptapsend/react-native that referenced this issue Aug 2, 2018
Summary:
@public
This should fix facebook#18403.
When the user is inputting in Chinese/Japanese with <TextInput> in a controlled manner, the RCTBaseTextInputView will compare the JS-generated attributed string against the TextInputView attributed string and repeatedly overwrite the TextInputView one. This is because the native TextInputView will provide extra styling to show that some text is provisional.
My solution is to do a plain text string comparison at this point, like how we do for dictation.

Expected behavior when typing in a language that has "multistage" text input: For instance, in Chinese/Japanese it's common to type out the pronunciation for a word and then choose the appropriate word from above the keyboard. In this model, the "pronunciation" shows up in the text box first and then is replaced with the chosen word.
Using the word Japan which is written 日本 but first typed as にほん. It takes 4 key-presses to get to 日本, since に, ほ, ん, are all typed and then 日本 is selected. So here is what should happen:

1. enter に, onChange fires with 'に', markedTextRange covers 'に'
2. enter ほ, onChange fires with 'にほ', markedTextRange covers 'にほ'
3. enter ん, onChange fires with 'にほん', markedTextRange covers 'にほん'
4. user selects 日本 from the menu above the keyboard (provided by the keyboard/OS), onChange fires with '日本', markedTextRange is removed

previously we were overwriting the attributed text which would remove the markedTextRange, preventing the user from selecting 日本 from above the keyboard.

Cheekily, I've also fixed an issue with secure text entry as it's the same type of problem.

Reviewed By: PeteTheHeat

Differential Revision: D9002295

fbshipit-source-id: 7304ede055f301dab9ce1ea70f65308f2a4b4a8f
lhcn added a commit to lhcn/react-native that referenced this issue Aug 7, 2018
@Taphood
Copy link

Taphood commented Aug 8, 2018

Which official release is going to have this bug fix? In RN0.56 I'm still seeing this issue

@krizpoon
Copy link

krizpoon commented Aug 9, 2018

Based on @tsangwailam 's solution, I came up with the following component. It works as a drop-in replacement (almost, except for the ref prop). It would work with external changes to the text value too.

import React, { PureComponent } from 'react';
import { TextInput, Platform } from 'react-native';
import ReactNativeVersion from 'react-native/Libraries/Core/ReactNativeVersion';

export function fixComposeInput(Component) {
    return class MyTextInput extends PureComponent {
        state = { diffKey: 0, value: '', display: '' };

        static getDerivedStateFromProps(props, state) {

            if (!state || !state.props || props.value !== state.props.value) {

                const value = props.value || '';
                const display = state && state.display || '';
                if (value !== display) {
                    const diffKey = ((state && state.diffKey) >>> 0) + 1;
                    return { props, value, display: value, diffKey };
                }
            }

            return { props };
        }

        handleChange = text => {
            // keep track of the display value
            this.setState({ display: text }, () => {
                const { onChangeText } = this.props;
                onChangeText && onChangeText(text);
            });
        };

        render() {
            const { refInput, value: valueProp, onChangeText, ...inputProps } = this.props;
            const { value, diffKey } = this.state;

            return <Component
                { ...inputProps }
                key={ `TextInput${diffKey}` }
                ref={ refInput }
                value={ value }
                onChangeText={ this.handleChange }
            />;
        }
    };
}

const rnVer = ReactNativeVersion.version.minor;

export default (Platform.OS === 'ios' && rnVer >= 54 && rnVer <= 56 ? fixComposeInput(TextInput) : TextInput);

sidnair pushed a commit to taptapsend/react-native that referenced this issue Aug 21, 2018
Summary:
@public
This should fix facebook#18403.
When the user is inputting in Chinese/Japanese with <TextInput> in a controlled manner, the RCTBaseTextInputView will compare the JS-generated attributed string against the TextInputView attributed string and repeatedly overwrite the TextInputView one. This is because the native TextInputView will provide extra styling to show that some text is provisional.
My solution is to do a plain text string comparison at this point, like how we do for dictation.

Expected behavior when typing in a language that has "multistage" text input: For instance, in Chinese/Japanese it's common to type out the pronunciation for a word and then choose the appropriate word from above the keyboard. In this model, the "pronunciation" shows up in the text box first and then is replaced with the chosen word.
Using the word Japan which is written 日本 but first typed as にほん. It takes 4 key-presses to get to 日本, since に, ほ, ん, are all typed and then 日本 is selected. So here is what should happen:

1. enter に, onChange fires with 'に', markedTextRange covers 'に'
2. enter ほ, onChange fires with 'にほ', markedTextRange covers 'にほ'
3. enter ん, onChange fires with 'にほん', markedTextRange covers 'にほん'
4. user selects 日本 from the menu above the keyboard (provided by the keyboard/OS), onChange fires with '日本', markedTextRange is removed

previously we were overwriting the attributed text which would remove the markedTextRange, preventing the user from selecting 日本 from above the keyboard.

Cheekily, I've also fixed an issue with secure text entry as it's the same type of problem.

Reviewed By: PeteTheHeat

Differential Revision: D9002295

fbshipit-source-id: 7304ede055f301dab9ce1ea70f65308f2a4b4a8f
MarkRunWu pushed a commit to MarkRunWu/react-native that referenced this issue Aug 24, 2018
Summary:
@public
This should fix facebook#18403.
When the user is inputting in Chinese/Japanese with <TextInput> in a controlled manner, the RCTBaseTextInputView will compare the JS-generated attributed string against the TextInputView attributed string and repeatedly overwrite the TextInputView one. This is because the native TextInputView will provide extra styling to show that some text is provisional.
My solution is to do a plain text string comparison at this point, like how we do for dictation.

Expected behavior when typing in a language that has "multistage" text input: For instance, in Chinese/Japanese it's common to type out the pronunciation for a word and then choose the appropriate word from above the keyboard. In this model, the "pronunciation" shows up in the text box first and then is replaced with the chosen word.
Using the word Japan which is written 日本 but first typed as にほん. It takes 4 key-presses to get to 日本, since に, ほ, ん, are all typed and then 日本 is selected. So here is what should happen:

1. enter に, onChange fires with 'に', markedTextRange covers 'に'
2. enter ほ, onChange fires with 'にほ', markedTextRange covers 'にほ'
3. enter ん, onChange fires with 'にほん', markedTextRange covers 'にほん'
4. user selects 日本 from the menu above the keyboard (provided by the keyboard/OS), onChange fires with '日本', markedTextRange is removed

previously we were overwriting the attributed text which would remove the markedTextRange, preventing the user from selecting 日本 from above the keyboard.

Cheekily, I've also fixed an issue with secure text entry as it's the same type of problem.

Reviewed By: PeteTheHeat

Differential Revision: D9002295

fbshipit-source-id: 7304ede055f301dab9ce1ea70f65308f2a4b4a8f
MarkRunWu pushed a commit to MarkRunWu/react-native that referenced this issue Aug 24, 2018
Summary:
@public
This should fix facebook#18403.
When the user is inputting in Chinese/Japanese with <TextInput> in a controlled manner, the RCTBaseTextInputView will compare the JS-generated attributed string against the TextInputView attributed string and repeatedly overwrite the TextInputView one. This is because the native TextInputView will provide extra styling to show that some text is provisional.
My solution is to do a plain text string comparison at this point, like how we do for dictation.

Expected behavior when typing in a language that has "multistage" text input: For instance, in Chinese/Japanese it's common to type out the pronunciation for a word and then choose the appropriate word from above the keyboard. In this model, the "pronunciation" shows up in the text box first and then is replaced with the chosen word.
Using the word Japan which is written 日本 but first typed as にほん. It takes 4 key-presses to get to 日本, since に, ほ, ん, are all typed and then 日本 is selected. So here is what should happen:

1. enter に, onChange fires with 'に', markedTextRange covers 'に'
2. enter ほ, onChange fires with 'にほ', markedTextRange covers 'にほ'
3. enter ん, onChange fires with 'にほん', markedTextRange covers 'にほん'
4. user selects 日本 from the menu above the keyboard (provided by the keyboard/OS), onChange fires with '日本', markedTextRange is removed

previously we were overwriting the attributed text which would remove the markedTextRange, preventing the user from selecting 日本 from above the keyboard.

Cheekily, I've also fixed an issue with secure text entry as it's the same type of problem.

Reviewed By: PeteTheHeat

Differential Revision: D9002295

fbshipit-source-id: 7304ede055f301dab9ce1ea70f65308f2a4b4a8f
ide pushed a commit to expo/react-native that referenced this issue Aug 31, 2018
Summary:
@public
This should fix facebook#18403.
When the user is inputting in Chinese/Japanese with <TextInput> in a controlled manner, the RCTBaseTextInputView will compare the JS-generated attributed string against the TextInputView attributed string and repeatedly overwrite the TextInputView one. This is because the native TextInputView will provide extra styling to show that some text is provisional.
My solution is to do a plain text string comparison at this point, like how we do for dictation.

Expected behavior when typing in a language that has "multistage" text input: For instance, in Chinese/Japanese it's common to type out the pronunciation for a word and then choose the appropriate word from above the keyboard. In this model, the "pronunciation" shows up in the text box first and then is replaced with the chosen word.
Using the word Japan which is written 日本 but first typed as にほん. It takes 4 key-presses to get to 日本, since に, ほ, ん, are all typed and then 日本 is selected. So here is what should happen:

1. enter に, onChange fires with 'に', markedTextRange covers 'に'
2. enter ほ, onChange fires with 'にほ', markedTextRange covers 'にほ'
3. enter ん, onChange fires with 'にほん', markedTextRange covers 'にほん'
4. user selects 日本 from the menu above the keyboard (provided by the keyboard/OS), onChange fires with '日本', markedTextRange is removed

previously we were overwriting the attributed text which would remove the markedTextRange, preventing the user from selecting 日本 from above the keyboard.

Cheekily, I've also fixed an issue with secure text entry as it's the same type of problem.

Reviewed By: PeteTheHeat

Differential Revision: D9002295

fbshipit-source-id: 7304ede055f301dab9ce1ea70f65308f2a4b4a8f
Fanghao pushed a commit to discord/react-native that referenced this issue Sep 6, 2018
Summary:
@public
This should fix facebook#18403.
When the user is inputting in Chinese/Japanese with <TextInput> in a controlled manner, the RCTBaseTextInputView will compare the JS-generated attributed string against the TextInputView attributed string and repeatedly overwrite the TextInputView one. This is because the native TextInputView will provide extra styling to show that some text is provisional.
My solution is to do a plain text string comparison at this point, like how we do for dictation.

Expected behavior when typing in a language that has "multistage" text input: For instance, in Chinese/Japanese it's common to type out the pronunciation for a word and then choose the appropriate word from above the keyboard. In this model, the "pronunciation" shows up in the text box first and then is replaced with the chosen word.
Using the word Japan which is written 日本 but first typed as にほん. It takes 4 key-presses to get to 日本, since に, ほ, ん, are all typed and then 日本 is selected. So here is what should happen:

1. enter に, onChange fires with 'に', markedTextRange covers 'に'
2. enter ほ, onChange fires with 'にほ', markedTextRange covers 'にほ'
3. enter ん, onChange fires with 'にほん', markedTextRange covers 'にほん'
4. user selects 日本 from the menu above the keyboard (provided by the keyboard/OS), onChange fires with '日本', markedTextRange is removed

previously we were overwriting the attributed text which would remove the markedTextRange, preventing the user from selecting 日本 from above the keyboard.

Cheekily, I've also fixed an issue with secure text entry as it's the same type of problem.

Reviewed By: PeteTheHeat

Differential Revision: D9002295

fbshipit-source-id: 7304ede055f301dab9ce1ea70f65308f2a4b4a8f
@facebook facebook locked as resolved and limited conversation to collaborators Jul 30, 2019
@react-native-bot react-native-bot added the Resolution: Locked This issue was locked by the bot. label Jul 30, 2019
1uokun referenced this issue in ant-design/ant-design-mobile-rn Aug 22, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Component: TextInput Related to the TextInput component. Impact: Regression Describes a behavior that used to work on a prior release, but stopped working recently. Issue: Author Provided Repro This issue can be reproduced in Snack or an attached project. Platform: iOS iOS applications. Ran Commands One of our bots successfully processed a command. Resolution: Fixed A PR that fixes this issue has been merged. Resolution: Locked This issue was locked by the bot.
Projects
None yet
Development

Successfully merging a pull request may close this issue.