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

TextInput becomes slow after lots of typing #20119

Open
gnprice opened this issue Jul 9, 2018 · 98 comments
Open

TextInput becomes slow after lots of typing #20119

gnprice opened this issue Jul 9, 2018 · 98 comments
Labels
Bug Component: TextInput Related to the TextInput component. Impact: Regression Describes a behavior that used to work on a prior release, but stopped working recently. Platform: Android Android applications. Ran Commands One of our bots successfully processed a command.

Comments

@gnprice
Copy link
Contributor

gnprice commented Jul 9, 2018

Environment

$ react-native info
Environment:
  OS: Linux 4.9
  Node: 8.11.3
  Yarn: 1.7.0
  npm: 5.6.0
  Watchman: Not Found
  Xcode: N/A
  Android Studio: Not Found

Packages: (wanted => installed)
  react: 16.3.1 => 16.3.1
  react-native: 0.56.0 => 0.56.0 (*)

(*) In my test app, actually v0.55.4 plus the patch from #19645 . Others on #19126 report the same symptoms using v0.56.

Description

After typing a lot of text into a controlled TextInput, like ~500 char on a recent fast phone, we start dropping frames. It gets worse the more you type; at ~1000 char, we very frequently drop frames and the app looks noticeably laggy.

Reproducible Demo

The original repro is by @s-nel: https://github.com/s-nel/rn19126

Originally reported at #19126 (comment); more discussion in subsequent comments in that thread.

In particular, here's the video from that repro:
video

Here's a video by @s-nel on my test app, with this description:

Yes I can reproduce from [that repo] after typing for a couple minutes straight. I don't have to clear anything as the original bug description [in #19126] indicates. Here you can see my JS framerate drop to single digits from adding a few characters

image

Here's my description of replicating that repro, again in my test app:

I just tried again with my test app, on RN 0.55.4 + patch. If I type continuously for about 60 seconds -- maybe 400-500 characters, at a rough estimate (just gibberish, both thumbs constantly typing letters) -- then the perf overlay gets to about "10 dropped so far", and zero stutters. If I continue and get up to about 90 seconds, the "dropped" figure climbs faster, and the framerate (in "JS: __ fps") noticeably drops. At about 150 seconds (~1000 characters?), the "dropped" figure rapidly climbs past 200, and the framerate hangs out around 30.

Even at that point, it's still "0 stutters". And if I ignore the perf overlay and try typing normal text and watching it like I were using an app for real: it's a bit laggy, but doesn't make things feel unusable or outright broken, and I think most of the time I wouldn't even notice.

That was on a Pixel 2 XL; the numbers will probably vary with hardware.

Background from previous bug

This bug seems closely related to #19126; the repro steps and symptoms seem to be exactly the same, except quantitatively much less severe. (The original reports of #19126 describe clearing the input as part of the repro; I now think that was probably a red herring.)

The extreme symptoms of #19126 were absent in v0.54, introduced in v0.55, and fixed in v0.56 by #19645 . As I wrote when I figured out the cause of that (leading to the one-line fix in #19645):

[This buggy NaN comparison] means every text shadow node will create a CustomLetterSpacingSpan around its contents, even when the letterSpacing prop was never set. It must be that we're somehow ending up with large numbers of these shadow nodes -- that sounds like a bug in itself, but I guess it's normally low-impact -- and this condition is failing to prune them from causing a bunch of work here.

I think we're now looking at exactly that underlying bug. It may well have been present in v0.54 and earlier releases; I don't think we have any data on that. [EDIT: @piotrpazola finds below that the bug is present in v0.50, and absent in v0.49.]

I don't know anything more about what causes it; even the hypothesis that we're ending up with large numbers of text shadow nodes is just an inference from how it interacts with the line fixed in #19645. So, more debugging is required.

I don't expect to do that debugging myself, because these remaining symptoms are no longer one of the top issues in our own app. But I hope somebody else will!

@react-native-bot react-native-bot added ⏪Old Version Component: TextInput Related to the TextInput component. Platform: Linux Building on Linux. labels Jul 9, 2018
@kelset kelset added JavaScript Impact: Regression Describes a behavior that used to work on a prior release, but stopped working recently. and removed ⏪Old Version Platform: Linux Building on Linux. labels Jul 10, 2018
@kelset
Copy link
Contributor

kelset commented Jul 10, 2018

👋 @gnprice thanks for the writeup, I've added a couple more tags that seem to be more related to the issue.

Sadly AFAIK the TextInput is an hard portion to work with, and currently there is a bit of bus factor in terms of devs that know how it works "deeply". So it may take some time before this get fixed, fingers crossed.

@alp1396
Copy link

alp1396 commented Jul 10, 2018

After few tests I`ve discovered that clean InputText (without any styling) works normally (sometimes fps going down, but app is not freezing). But if I add some styling to input (e.g. fontSize) then typing process is going to be laggy more and more.

Unstable behavior is appear much faster if quantity of styling options is more. It seems that something went wrong with styling span generation (size, lineHeight, letterSpace and so on) inside of the logic of the text shadow nodes code.

For now, i have to remove all styling from inputs, to get app works.

Maybe that helps someone. Cheers!

@alp1396

This comment has been minimized.

@react-native-bot

This comment has been minimized.

@kelset
Copy link
Contributor

kelset commented Jul 27, 2018

(ok ignore the bot)

@kelset kelset reopened this Jul 27, 2018
@kelset kelset removed ⏪Old Version Platform: Linux Building on Linux. labels Jul 27, 2018
@atopus
Copy link

atopus commented Aug 16, 2018

Hi,

I'm experiencing the same problem with 3 separate react native apps under developpement.

I have conducted my small tests with the slightlyest modified basic App (but trying with "PureComponent" - see below) with the following versions :

  • 0.54.0
  • 0.55.x series
  • and the 0.56.1 version.

There are differences, but as far as I can tell, the behaviour is basically the same : performances drop after a couple of hundreds of typing (300 words / 1000 carateres). So, could this be inherent to react native &/or mobile dev ?

There are aggravating factors, though :

  • Maybe, as @alp1396 reported above, text styling is a "bug" amplifier, I haven' tried
  • With 0.55.x (not tested with 0.54 nor 0.56), when <TextInput onChange={this._onChange} value={this.state.value} /> is replaced by <TextInput onChangeText={this._onChange}>{this.state.value}</TextInput>, performances are dramatically decreased (but this is not "by the book" anyway). 0fp is reached after 500 caracters or so. ;
  • With no value={this.state.value} inside the TextInput component, it is much better. At 1200 caracters, it is still around 45-55fps, which is acceptable.

My projects must allow the user to compose large pieces of texts, so I guess I will keep an eye on it, and try various workarounds to mitigate it (unmount the component + no value={this.state.value} when admissible...).

Thanks a lot for any update on this issue.


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

export default class App extends React.PureComponent {

  constructor(props) {
    super(props)
    this.state = {
      label : ''
    }
  }

  _onChange = (label) => {
    this.setState({ label })
  }

  render() {
    return (
      <View style={styles.container}>
       <Text>Caracter count : {this.state.label.length}</Text>
        <Text>Word count : {this.state.label.split(' ').length}</Text>
        <TextInput 
          onChangeText={this._onChange}
          value={this.state.label}
        />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

@pluberski
Copy link

I don't know if this is in any way helpful, but I started dev-ing on an earlier RN version for my app (I think it might have been .53) and didn't notice the slowdown issue until after I had upgraded my RN version to .55 (and now also seeing it in .56). To wit, not 100% SURE it wasn't there back then, but it does make me wonder if something introduced in .54 might be causing the issue.

@stueynet
Copy link

This issue started in 0.54 and still exists today. Any chat app that uses a text input that persists between messages becomes unusable after only a few minutes if chatting. I wish there was some way to get this fixed.

@s-nel
Copy link

s-nel commented Aug 19, 2018

@stueynet a workaround for chat is to force React to unmount and remount on the text input when the user sends a message.

@jerson-av
Copy link

for me the solution was to do the following

https://snack.expo.io/@jerson/text-input-slow-fix

 _onChange = (label) => {
     if (this.timeoutUpdate) {
      clearTimeout(this.timeoutUpdate);
    }
    this.timeoutUpdate = setTimeout(() => {
      this.input.setNativeProps({
        text: label
      });
    }, 100);

    this.setState({ label })
  }

@cweilguny
Copy link

cweilguny commented Sep 7, 2018

Any updates on that? Still reproducible in 0.57rc4. Just create a new react native project and use this simple App class:

export default class App extends Component<Props> {
    render() {
        return (
            <TextInput />
        );
    }
}

As the TextInput is unstyled, you won't see it, just click in the top left corner, then the cursor appears. After several recursions (3-5) of typing and clearing the whole text, the lag is noticable, from then on the lag increases every time you clear. On our test device, also other apps are slow then, e.g. the Chrome address bar lags just like the RN TextInput. Only a restart of the device fixes it, but it appears again after some recursions of typing and clearing.

@jenni-divvito
Copy link

OK I have been elbows deep in this issue for a week, I would not have considered myself strong on the Native code side of things, but I think I can at least confirm the original hypothesis that large numbers of spans are being created...

By attaching a breakpoint in /com/facebook/react/views/textinput/ReactEditText.class:395 (manageSpans() method) I can see that as the input is used the number of CustomLineHeight spans on it grows continuously, not being removed when the input is cleared or the text the span affects is deleted. From my understanding this could be other styling spans too - it just happens to be customLineHeight spans in my case because that is the styling I am using.

From what I can tell this is caused by the combination of a few things -

  1. The CustomLineHeight class is not one of the classes checked at the start of manageSpans() in:
      // Remove all styling spans we might have previously set
      if (ForegroundColorSpan.class.isInstance(spans[spanIdx]) ||
          BackgroundColorSpan.class.isInstance(spans[spanIdx]) ||
          AbsoluteSizeSpan.class.isInstance(spans[spanIdx]) ||
          CustomStyleSpan.class.isInstance(spans[spanIdx]) ||
          ReactTagSpan.class.isInstance(spans[spanIdx])) {
        getText().removeSpan(spans[spanIdx]);
      }
  1. Although in sameTextForSpan() it specifically checks to see if the span is for a section of text that no longer exists, and does not add the span to the new SpannableStringBuilder it is building to replace the existing text with, this doesn't help because...
  2. back in maybeSetText() we call getText().replace(0, length(), spannableStringBuilder); however this replaces the text spans from 0 to the new length, not the old one, leaving the spans related to deleted text alive and kicking.

I tried changing the second param to the previous length of the text, however unfortunately this makes the Editable throw and out of range exception as it already considers it's length to be that of the new text (Even though it is still hanging on to the references to the spans that run outside of that new text's bounds).

What did seem to help for me was to add CustomLineHeight as one of the classes to check for the removal of style spans. As above - this just happens to be the styling spans that I am using, but other use cases may need other span classes added there. Seeing as the span is being removed directly, rather than replaced, it seems to work out.

What this does not explain is the experiencing of this issue without clearing or deleting text - that I have no clue about - but certainly as per @Netdesk and @stueynet above I was still seeing the performance issues after clearing text even after applying the patch from #19645 .

Hopefully this points someone with more knowledge of the Android TextInput code in the right direction

tarouboy referenced this issue in tarouboy/react-native Feb 17, 2021
@davidvasquezr
Copy link

In my case, the TextInput becomes slow when starting typing. I noticed that autocorrect takes some time to suggest words on the keyboard and so I turned it off. Now it works better. I know it's not the best solution, but ... ¯ \ _ (ツ) _ / ¯

autoCorrect={false}

@scottmas
Copy link

This has gotten worse recently since we've upgraded to RN 67 and we've begun receiving reports of more lag and even crashing ("App isn't responding...") . None of the fixes suggested above seem to work. Any updates on RN core side @kelset @bvaughn, etc?

@lmarques6
Copy link

lmarques6 commented Mar 18, 2022

Not the ideal solution, but I've also found that turning off autoCorrect as suggested above has a significant positive performance impact with large text.

Device: Samsung Galaxy S10
Android Version: 12
React-Native Version: 0.64.0

@github-actions
Copy link

github-actions bot commented Mar 3, 2023

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.

@github-actions github-actions bot added the Stale There has been a lack of activity on this issue and it may be closed soon. label Mar 3, 2023
@gnprice
Copy link
Contributor Author

gnprice commented Mar 3, 2023

There's no reason to believe this has been fixed. Stale-bot please go away.

@github-actions github-actions bot removed the Stale There has been a lack of activity on this issue and it may be closed soon. label Mar 3, 2023
@RemyLivewall
Copy link

We are having similar issues on React Native 0.67 and Android 13 users.

We have a chat screen with a TextInput at the bottom. Removing everything but style, multiline and scrollEnabled results in the app suddenly slowing down after about one minute of doing nothing or typing a few letters. Dismounting the input by leaving the screen restores any slowdown. The TextInput simply seems to implode even without reading or writing to it via the onChangeText, defaultValue or value props.

@shahanshah-cutshort
Copy link

We are having similar issues on React Native 0.67 and Android 13 users.

We have a chat screen with a TextInput at the bottom. Removing everything but style, multiline and scrollEnabled results in the app suddenly slowing down after about one minute of doing nothing or typing a few letters. Dismounting the input by leaving the screen restores any slowdown. The TextInput simply seems to implode even without reading or writing to it via the onChangeText, defaultValue or value props.

Did you find any solution for this?

@cweilguny
Copy link

How can such an essential component be buggy since 5 years? Not asking to annoy somebody, just questioning, if there's something wrong with notifications, some missing label, or whatever is needed that this issue is seen by some maintainer.

@mfaghfoory
Copy link

we had to rewrite the input component in our app 4 years ago! it wonders me that I get a notification from this topic once in a while and seems like the original issue has not been fixed yet!

@bdokimakis
Copy link

we had to rewrite the input component in our app 4 years ago! it wonders me that I get a notification from this topic once in a while and seems like the original issue has not been fixed yet!

Would you be willing to share the fixed component?

@mfaghfoory
Copy link

we had to rewrite the input component in our app 4 years ago! it wonders me that I get a notification from this topic once in a while and seems like the original issue has not been fixed yet!

Would you be willing to share the fixed component?

unfortunately, I don't have access to the code since I left the company 3 years age

@arthurgithub123
Copy link

The problem seems to solve for me (but I'll still test a lot more).
Solved when I added:
scrollEnabled={false}
autoCorrect={false}
to the TextInput

If you want to understand the app and the problem I had:

In my app I have a component named Exercise with View, several Text and a TextInput.
Each one with its styles and TextInput with also onFocus and onBlur.
In the Exercises page you choose how many exercises you want. It means several or many
Exercise components will be rendered in the Exercises page.
And when input the answers in the TextInputs of each Exercise you can check if the answers are
right or not and generate new exercises.
When you generate new it means what you generated before will be removed and the new ones
will be rendered.

before scrollEnabled={false} and autoCorrect={false} I stopped using state and tried ref but did not work.
Now I'm using scrollEnabled={false} autoCorrect={false} and ref.
But did not test with scrollEnabled={false} autoCorrect={false} and state.

The problem:
You could generate just 3 exercises and you input the answer in TextInputs. At the beginning the text
digits fast but after some time generating more 3 exercises it became very slow and did not come
back to normal. And it affects other apps. Going to Whatsapp became very slow to digit. I had to
restart cellphone.

@holmesjr
Copy link

holmesjr commented Sep 5, 2023

I've still seen this in RN 0.70 (within an expo app on Android).

I took a few approaches:

  • Upgrade to RN 0.71 / Expo SDK 48
  • Remove "value", instead using "defaultValue"
  • Turning off autocorrect
  • Aggressively memoising the input, so it does not re-render at all while it updates state in onChange()

I'll see how this goes, but I've had good reports so far.

@fabOnReact
Copy link
Contributor

fabOnReact commented Jan 19, 2024

See more info in this comment #20119 (comment)
It is caused by TextInput controlled components.

Possible solution:
When we reach a lot of update and consequential slow down in the performance, instead of batching 1 update for each letter we type (onChangeText triggers every time we type a character), we send multiple characters all at once

For example

  1. User types in the TextInput "Hello World"

Now react-native does like this:

  1. JS TextInput sends "H" to Java EditText
  2. Java EditText shows H in the TextInput
  3. Java notifies JS TextInput that H was added on the EditText
  4. JS repeats 2-4 for every character

Instead we could add logic in the TextInput API, in case user types 5 characters per second:

  1. JS TextInput sends "Hello" to Java EditText
  2. Java EditText shows Hello in the TextInput
  3. Java notifies JS TextInput that Hello was added on the EditText
  4. JS repeats 2-4 for the second word "World"

It could enabled by default or disabled by default with a prop batchWordsToOptimizePerformanced.
The batching of words instead of characters would be used ONLY when the user types extremely fast and the TextInput is very laggy.

The issue was opened 6 years ago.
It should have been resolved with the new architecture. Does somebody experience this with new architecture?
Should I prepare a fix or nobody is interested in this anymore? Thanks

@fabOnReact
Copy link
Contributor

  1. I would not take into consideration implementing the above solution. Rendering words instead of characters, when TextInput performances are degraded, may not be a good option.
  2. Implementing a setTextSync is not possible on the old architecture (see my comment above)

Does it reproduce on the new architecture?

@react-native-bot
Copy link
Collaborator

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.

@react-native-bot react-native-bot added the Stale There has been a lack of activity on this issue and it may be closed soon. label Jul 31, 2024
@gnprice
Copy link
Contributor Author

gnprice commented Jul 31, 2024

Stale-bot please go away — there's no reason to think this has been fixed.

@react-native-bot react-native-bot removed the Stale There has been a lack of activity on this issue and it may be closed soon. label Jul 31, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Component: TextInput Related to the TextInput component. Impact: Regression Describes a behavior that used to work on a prior release, but stopped working recently. Platform: Android Android applications. Ran Commands One of our bots successfully processed a command.
Projects
None yet
Development

No branches or pull requests