react-native: Duplicated letters when autoCapitalize="characters" on android

Description

When trying to implement an upper case only input, adding autoCapitalize="characters" seems to work on Android by setting the keyboard to be upper case by default. One can hit the shift key and then type a lower case letter.

To ensure that we only let the user enter (and see) upper case letters, I thought that I might be able to handle it by ensuring that when we update the react state of the component we capture the text in upper case.

By using a toUpperCase on top of what is a pretty standard state update cycle (i.e. a very similar update method ala the examples at https://facebook.github.io/react-native/releases/next/docs/textinput.html ), this saves the input into the state, uppercased, ready for the next render cycle. (I’m not concerned about the dangers of toUpperCase at this point.)

Unfortunately, the behaviour is a bit strange when you start typing both upper and lowercase letters, where you start getting repeated letters, e.g. if I type AbC, I will end up with ABABC, AbcdE I get ABABCDABABCDE.

Reproduction

I created an example app here: https://rnplay.org/apps/t-gBOA Note that the behaviour seems fine on the iOS simulator, but is ‘wrong’ on the android simulator.

or see the component below:

import React, {Component} from "react";
import {View, TextInput} from "react-native";

export class UpperCaseTextEntry extends Component {
    
    constructor() {
        super();
        this.state = {
            text: ""
        }
    }
    
    upperCaseIt(text) {
        var textUpperCase = text.toUpperCase();

        this.setState({text: textUpperCase});
    }
    
    render() {
        var text = this.state.text;
        return (
            <View>
                <TextInput value={text} autoCapitalize="characters"
                           onChangeText={(text) => {this.upperCaseIt(text)}}
                />
            </View>
        )
        
    }
}

Solution

I suspect that there’s something going awry with the syncing of state between the react state and the state of the underlying components, quite possibly some case-ignoring checks are being in some places, but not others.

Additional Information

  • React Native version: 0.35.0
  • Platform: Android
  • Operating System: Windows (building/testing on windows)

I’ve also noted during that it’s more than possible to fire multiple onChangeTexts before a render is triggered, which could lead to unexpected app behaviour. This could be expected unexpected behaviour though 😃

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 126
  • Comments: 120 (16 by maintainers)

Commits related to this issue

Most upvoted comments

secureTextEntry={Platform.OS === 'ios' ? false : true}
keyboardType={Platform.OS === 'ios' ? null : 'visible-password'}
autoCapitalize="characters"

This kinda makes the trick for both platforms

Hello, hope this could help anyone

I use react-native-elements Input component so basically i did this

I created a hook, then in my Input component I use onChangeText function, so I put this to clear the text previosly saved, and now set the new text again in uppercase.

const [textInput, setTextInput] = useState(‘’);

<Input value={textInput} onChangeText={async text => { await setTextInput(‘’); await setTextInput(text.toUpperCase()); }} />

Hello from 2023 😄

Try to set autoCorrect to false and check if problem exist yet.

How is this still a thing 5 years later?

Just to remember that testing it on emulators will not show the real problem, I tested on my real device and the problem occurs.

For me It still happens on both. =(

I’ve managed to find the bug and in runtime trick it to make it work…

Apparently, when we setText natively, we have two different text informations. (AGAIN, THIS BUG ONLY HAPPENS WHEN YOU USE UPPERCASE OF LOWERCASE)

So, I’ve end up on inner android classes and I’ve noticed that the dupe of characters happens on this line: image

The reason why it happens is that we have two different texts. Let’s say that we’ve added “a” and then after onTextChange JavaScript event, we changed it to “A”… On the native side, even though we see “A”, somehow “a” still there. When this line is executed: image

We still are going to be able to see it on the visual element displaying the correct char.(line after) image

Now, after this, we should have two versions of the text, therefore, the next char will be duped as we remained with “a” inside the native component and “A” on the visual part… Here is the proof that we remained with “a” instead of “A” inside of the native component somehow: image

Now, on: BaseInputConnection.java@843, it will try to replace the text “A” for “ab”: image

Logically, replacing “A”@Position:1, by “ab”, we will end up with: “Aab”…

(Variables at the moment:) image image image

AAAnddd, voila: image (Our text is now “Aab”)

After all of that, our text.toUpperCase(), will be fired on JavaScript side, and the method receiver on native side is: maybeSetText@ReactEdittext.java:359 image Then if we inspect getText(), this instance will be with our correct text, but somehow, it will break when changing by default Android classes… image

AND, IF WE execute this line every time on the Evaluate screen right after getText().replace… image

The bug disappears: image

Are there any updates on these issue already? , I am also experiencing this.

still happens guys in 2022

Has anyone found a solution for this bug?, I have the same problem in all samsung that have the predictive language activated

Any real solution for this?

found only this solution, add these props to your TextInput

autoCapitalize="none"
secureTextEntry={true}
keyboardType={"visible-password"}

having same issue with lowercasing, every time a capital case character is entered, the text that is passed to onChangeText function contains exact duplicate of existing text plus last entered character. it happens only on android devices, on ios it works fine

autoCorrect set to false fixed the issue for me…

In 2027 I will be fixed

This problem still happen with capitalized letters in any situation.

Any update on this issue . I just tried to use toUpperCase on my textinput. Its dupllicates the characters.

const [textInput, setTextInput] = useState(‘’); <Input value={textInput} onChangeText={async text => { await setTextInput(‘’); await setTextInput(text.toUpperCase()); }} />

This solution worked well if Keyboard capital is on. If user deactivate capital on their keyboard, duplicate letters inputted.

This solution worked very well, I encapsulated it in a custom InputText component, in the tests I did it worked very well, both for capitalization of letters and for masking numerical values, a context where I had a similar problem: import { forwardRef, useEffect, useState, useRef, useLayoutEffect } from “react”; import { TextInput as DefaultTextInput, TextInputProps } from “react-native”; function useAsyncState<T = undefined>(defaultValue: T | (() => T)): [T, ((value: T) => Promise)] {

This solution worked for me. But there is a drawback, typing becomes laggy. If I typed fast, some characters are not inputted

secureTextEntry={Platform.OS === 'ios' ? false : true}
keyboardType={Platform.OS === 'ios' ? null : 'visible-password'}
autoCapitalize="characters"

Now this solution working perfectly for me… react-native version 0.67.2

Thanks!!

This last solution really solves the problem with no side effects, but it only solves it in the context of capitalizing the text, and this problem of doubling characters is also a problem that occurs when any value change is made at typing time, either to mask numeric values or something similar, I think the only definitive solution will only occur when react developers pay attention to the problem, or using a standard parallel input lib from react, until then there are only these quick solutions, but such a simple problem is frustrating It’s still there after so long.

const [textInput, setTextInput] = useState(‘’);

<Input value={textInput} onChangeText={async text => { await setTextInput(‘’); await setTextInput(text.toUpperCase()); }} />

This solution worked well if Keyboard capital is on. If user deactivate capital on their keyboard, duplicate letters inputted.

This solution worked very well, I encapsulated it in a custom InputText component, in the tests I did it worked very well, both for capitalization of letters and for masking numerical values, a context where I had a similar problem:

import { forwardRef, useEffect, useState, useRef, useLayoutEffect } from “react”; import { TextInput as DefaultTextInput, TextInputProps } from “react-native”;

function useAsyncState<T = undefined>(defaultValue: T | (() => T)): [T, ((value: T) => Promise<void>)] {

This solution worked for me. But there is a drawback, typing becomes laggy. If I typed fast, some characters are not inputted

secureTextEntry={Platform.OS === 'ios' ? false : true}
keyboardType={Platform.OS === 'ios' ? null : 'visible-password'}
autoCapitalize="characters"

Now this solution working perfectly for me… react-native version 0.67.2

Thanks!!

This problem persists on RN 62.2

@mauricioscotton Care to make a PR to react-native to close out this issue? Has been open for three years now.

If you cannot find the time, I can debug this and submit a PR so we can close this out and release this fix.

Truly shameful how long basic text input issues plague our Android product 😃 Coming back to this thread since over a year to see nothing has really improved.

The only workaround we found to make the experience bearable was to hide the TextInput behind a normal Text component that does all text rendering. Lost selection/copy/paste etc features, had to create a custom caret, but still works better than the TextInput…

Appears to be working on a Samsung S7 (React Native 0.57.4) with (suggested by nathvarun):

<TextInput> ... secureTextEntry={true} keyboardType="visible-password" </TextInput>

Hey folks, there’s an open PR for this

Hello, hope this could help anyone

I use react-native-elements Input component so basically i did this

I created a hook, then in my Input component I use onChangeText function, so I put this to clear the text previosly saved, and now set the new text again in uppercase.

const [textInput, setTextInput] = useState(‘’);

<Input value={textInput} onChangeText={async text => { await setTextInput(‘’); await setTextInput(text.toUpperCase()); }} />

This solution worked very well, I encapsulated it in a custom InputText component, in the tests I did it worked very well, both for capitalization of letters and for masking numerical values, a context where I had a similar problem:

import { forwardRef, useEffect, useState, useRef, useLayoutEffect } from "react";
import { TextInput as DefaultTextInput, TextInputProps } from "react-native";

function useAsyncState<T = undefined>(defaultValue: T | (() => T)): [T, ((value: T) => Promise<void>)] {

    const [value, setValue] = useState(defaultValue);
    const resolvesRef = useRef<(() => void)[]>([]).current;

    const setValueAsync = (newValue: T): Promise<void> => {
        return new Promise(resolve => {

            if (value === newValue) return resolve();

            resolvesRef.push(resolve);
            setValue(newValue);
        });
    };

    useEffect(() => {
        resolvesRef.forEach(resolve => resolve());
        resolvesRef.splice(0, resolvesRef.length);
    }, [value]);

    return [value, setValueAsync];
};

const TextInput = forwardRef<
    DefaultTextInput,
    TextInputProps
>(function ({ value, ...rest }: TextInputProps, ref: any) {

    const [stateValue, setStateValue] = useAsyncState(value);

    useLayoutEffect(() => {

        (async () => {
            await setStateValue('');
            await setStateValue(value);
        })();

    }, [value]);

    return (
        <DefaultTextInput
            {...rest}
            ref={ref}
            value={stateValue}
        />
    );
});

export default TextInput;

Then just import it and use it as the default TextInput of react:

<TextInput
    value={value}
    onChangeText={value => {
        setValue(value.toUpperCase())
   }}
   placeholder={'Value here'}
   style={styles.input}
 />
secureTextEntry={Platform.OS === 'ios' ? false : true}
keyboardType={Platform.OS === 'ios' ? null : 'visible-password'}
autoCapitalize="characters"

This kinda makes the trick for both platforms

This does not work for me…

This fix worked for us in RN 0.61, but not in RN 0.63 it seems. We got the same duplicating text again.

Removing the autoCapitalize entirely and using toLowerCase(); in an onchange function, and setting the state there did the job for us.

This issue still happens om 0.57.7… Exactly as shown by @john1jan

I upgraded my react-native to the latest version 0.55.4 but the issue still isn’t solved, any advice?

@MedinaGitHub of course the problem is the use of toUpperCase(). We use this to enforce uppercase and this causes this bug on Samsung devices.

the problem is toUpperCase() , i use <TextInput autoCapitalize=‘characters’ …

Same issue. Phone: Samsung Galaxy S7 / Moto Z2 Play Keyboards: Samsung Keyboard (with predict), Swiftkey Keyboard and Google Keyboard RN: react-native@0.47.1

Experiencing same with lowercase.

RN: 0.39.2 Phone: samsung galaxy S7

video testing the solution above

https://github.com/facebook/react-native/issues/11068#issuecomment-1072564012

<video src="https://user-images.githubusercontent.com/24992535/159202289-0ade72ba-f870-44a1-b350-8bafdbd7f3e8.mp4" width="1000" ></video>

setNativeProp is not available in Fabric

<image src="https://user-images.githubusercontent.com/24992535/159202953-94454477-c8aa-456e-9848-001c3399ffbd.png" width="250" />

https://github.com/facebook/react/issues/18499#issuecomment-650686427

I don’t have the time to verify, but I believe setNativeProp({ text }) skips the line of code causing this issue.

https://github.com/aosp-mirror/platform_frameworks_base/blob/1ac46f932ef88a8f96d652580d8105e361ffc842/core/java/android/widget/TextView.java#L6081-L6109 “AOSP method TextView setText” https://github.com/aosp-mirror/platform_frameworks_base/blob/1ac46f932ef88a8f96d652580d8105e361ffc842/core/java/android/widget/TextView.java#L2336-L2338 “AOSP method TextView getText”

I’m testing now with setTextKeepState to avoid changing the cursor position.

This worked for me:

import React from "react";
import { useRef, useState } from "react";
import { StyleSheet, TextInput, View, Platform } from "react-native";

const Index = () => {

    const [value, setValue] = useState();
    const textInputRef = useRef();

    return (
        <View style={styles.container}>
           <TextInput
               ref={textInputRef}
               value={Platform.OS === 'ios' ? undefined : ''}
               style={styles.input}
               onChangeText={val => {
                   const text = val.toUpperCase();
                   textInputRef.current && textInputRef.current.setNativeProps({ text });
                   setValue(text);
               }}
               placeholder={'Value here'}
            />
        </View>
    );
}

const styles = StyleSheet.create({
    input: {
        borderWidth: 1,
        height: 40
    },
    container: {
        flex: 1,
        padding: 10,
        justifyContent: 'center'
    }
});

export default Index;

O problema não é de toUpperCase. Qualquer função que obtenha as entradas do input por parâmetro irá duplicar caracteres. A solução é resetar o input. Existe uma lib para isso: react-native-text-input-reset

Still seeing this issue with React Native 0.56.0 😦

I have the same issue but found a workaround for my current needs.

The issue can’t be re-produce via the simulator using your keyboard. However, if you use the onscreen keyboard, then you can see the text is duplicated.

For my case, since the user can’t use a ‘keyboard’ except for the on-screen keyboard provided. In adding, it only became an issue when I use the text.toUpperCase() Thus, setting the text state in lower case and autoCapitalize="characters" seems to work fine for my case. Of course, if you do not want the user to ever be able to enter lowercase, then this is an issue.

PS: I specified a fontFamily in style and autoCapitalize still work as expected

It does. autoCorrect has nothing to do with it.

Same issue.

RN: 0.44.3 Phone: Galaxy S8

Cannot reproduce this in emulator, so maybe something Samsung specific?

great, thanks Fabrizio. There are soooo many open prs around TextXX. I have pinged meta team in a pre-release discussion about all of them but they decided to not include anything 🤷‍♂️ at that time. I’ll find the list and I’ll open an issue on react-native-improved.

Thanks @efstathiosntonas.

  1. To be included in the facebook/react-native release, the PR needs to be merged in main branch. We need a commit hash
  2. Meta uses react-native in Facebook Marketplace and other apps. Facebook Marketplace uses the main branch from facebook/react-native
  3. For this reason, it is hard to merge the PR in main branch. Meta react-native team also has limited resources to review and merge every PR.

Using inheritance, I can apply patches to react-native Text or TextInput component without building from source.

For example, I applied PR https://github.com/facebook/react-native/pull/41770 (Issue https://github.com/facebook/react-native/issues/39722) to react-native-improved using inheritance with ReactTextView, ReactTextViewManager and ReactTextShadowNode.

The project is a now proof of concept. I plan to complete the project in 1-2 months, for this reason I’m currently preparing a list of tasks with different priorities.

great, thanks Fabrizio. There are soooo many open prs around TextXX. I have pinged meta team in a pre-release discussion about all of them but they decided to not include anything 🤷‍♂️ at that time. I’ll find the list and I’ll open an issue on react-native-improved.

@fabriziobertoglio1987 are all these PRs included in the react-native-improved repo? 🤔

@lunaleaps I’ve encountered this issue on 0.64 but only on a Samsung device (via QA), and with “predictive text” enabled. On my Pixel 2XL, it runs a different keyboard and there isn’t a way to turn on predictive text afaik, so there isn’t a way to repro (looking at your video, it looks more like my Pixel keyboard rather than the Samsung keyboard). Turning off predictive text seems to fix the issue, as per:

When I disable the “Predictive Text” at “Language and Input > Samsung keyboard settings” the issue is solved, but how to solve that programmatically? 😕

Has anyone found a solution for this bug?, I have the same problem in all samsung that have the predictive language activated

Could you try repro’ing using a Samsung device?

I haven’t read enough of the codebase to contribute yet, but so far the behavior looks most similar to this breakdown of the situation: https://github.com/facebook/react-native/issues/11068#issuecomment-462778742

EDIT: Apparently samsung has an emulator online that has the Samsung keyboard (android emulator uses AOSP keyboard even on Samsung Tab S3) https://developer.samsung.com/remotetestlab/webclient/

I used this to test my stuff, I think maybe it either has something to do with the style textTransform: ‘uppercase’ (which transforms the actual value of the TextInput rather than it being a display transformation like in the browser, or it has something to do with doing .toUpperCase() inside onChangeText (I was able to transform the text within value={text.toUpperCase()}). I don’t have a Samsung device and I have limited credits so I’m not able to look into this further.

Solution:

onChangeText = (text) => {
  this.setState({ text })
}
render() {
  return (
    <TextInput
      onChangeText={this.setChangeText}
      autoCapitalize="characters" // this doesn't capitalize the text, but in iOS and Samsung it sets the keyboard to caps mode. in Pixel it doesn't, so we still need to uppercase it manually. its not necessary but I keep it in here because I like not having a flash of lowercase
      value={(text || '').toUpperCase()}
      maxLength={6} // you can probably truncate in render too using .slice(0, 6), but idk, this is what I use
    />
  )
}

I’m trying to verify this issue on 0.72 but I can’t reproduce with this functional component:

function UpperCaseTextEntry() {
  const [text, setText] = React.useState('');
  return (
    <View>
      <TextInput
        value={text}
        autoCapitalize="characters"
        onChangeText={t => {
          const uppercase = t.toUpperCase();
          setText(uppercase);
        }}
      />
    </View>
  );
}

Same with this version of the original repro

class UpperCaseTextEntry extends Component<{}, {text: string}> {
  constructor(props: {}) {
    super(props);
    this.state = {
      text: '',
    };
  }

  toUpperCase(text: string) {
    this.setState({text: text.toUpperCase()});
  } 

  render() {
    return (
      <View>
        <Text>My Attempt 2</Text>
        <TextInput
          value={this.state.text}
          autoCapitalize="characters"
          onChangeText={t => this.toUpperCase(t)}
        />
      </View>
    );
  }
}

Is anyone able to reproduce with either of the above?

https://github.com/facebook/react-native/assets/1309636/07a9e456-7ee9-4535-a86b-f8f9dc5526df

Any real solution for this?

found only this solution, add these props to your TextInput

autoCapitalize="none"
secureTextEntry={true}
keyboardType={"visible-password"}

yes, work on 0.64

Any real solution for this?

a colleague raised this issue today in a FB group… does the bug still exist?

Hi @MarcoScabbiolo I think it might be because the team relies on the community to resolve these bugs. See : https://github.com/facebook/react-native/issues/29815

Yup still ongoing. Most issues won’t get personal attention. We rely on the community to solve specific bugs like this with a PR

I encounter this issue only when the suggestions bar is activated. For example, when you add too many random characters and thus get no suggestions, the duplication behavior stops. You can further confirm this by disabling suggestion strip in Settings > system > languages & input > on-screen keyboard > Gboard > text correction > show suggestion strip

This worked for me:

secureTextEntry={Platform.OS === ‘ios’ ? false : true} keyboardType={Platform.OS === ‘ios’ ? null : ‘visible-password’}

It works because, for a password input, the suggestion strip is disabled.

secureTextEntry={Platform.OS === 'ios' ? false : true}
keyboardType={Platform.OS === 'ios' ? null : 'visible-password'}
autoCapitalize="characters"

This kinda makes the trick for both platforms

Works for me! Thanks, man!

works for me too!

secureTextEntry={Platform.OS === 'ios' ? false : true}
keyboardType={Platform.OS === 'ios' ? null : 'visible-password'}
autoCapitalize="characters"

This kinda makes the trick for both platforms

But also, unfortunately, takes control over crucial keyboard styling away from you.

I don’t really need to do that, i just gave up from this issue to focus in the others that are more important for the project, but it was an annoying bug where i was losing too much time, i just let the characters to be lowercased 😊. Thanks for the help.

Does anyone know any other way to solve this bug? I’ve tried all the resolutions mentioned here, but none of them worked for me. I’m trying to set my characters to upper case with textTransform: 'uppercase.

RN Version: 0.60.4

Mate! I’m also from RS! =D The resolution for android comes by touching RN java files. Give me a shout if you need some help with it.

The same on 0.57.4

Duplicate message on android when typing in Vietnamese keyboard, just on android, iOS work fine.

Not only Samsung

I stumbled upon the same issue. Found out the autoCapitalize was not working due to fontFamily specified in style prop.

Seems like it was failing silently. Will update here, if I find anything else.