facebook / react-native

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

TextInput becomes slow after lots of typing #20119

Open gnprice opened 6 years ago

gnprice commented 6 years ago

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 https://github.com/facebook/react-native/issues/19126#issuecomment-402904164; 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!

laurent22 commented 4 years ago

But also scary. I tried to make sure there were no behavior changes when I rewrote it

Here's hoping you don't restore the previous behavior 😄

Perhaps what was changed is that there was previously many unneeded renders on each key press, while with hooks you somehow avoided that?

rspenc29 commented 4 years ago

It also seems to be fixed on 0.61.5. Can anyone else confirm?

natasha08n commented 4 years ago

not 100% sure but seems it works good on 0.61.5, checked locally

kelset commented 4 years ago

That's great to hear! Given that we sort of have a 3-way confirmation on this not being an issue in latest versions, I'll be optimistic and close this issue.

If someone can repro this with latest 0.61.x AND 0.62 RC please link it below and we can reopen this.

rspenc29 commented 4 years ago

I think closing this is a little premature. This is a major issue that has affected the entire user base for several years. This issue alone has caused several developers i know personally to abandon react native due to frustration and hundreds of hours of lost productivity. I think it deserves some more thorough testing. And if it is fixed, maybe we can find the commit that fixed it and the reason it was broken in case it turns up again in the future.

kelset commented 4 years ago

Given that 3 different developers confirmed separately that the issue is fixed for them I thought it would be good to close it to signal to the other developers in the issue that there has been "an evolution" on this subject (as probably many people don't open the notification about it when simply new comments are added). But I see your point @rspenc29 so I'll reopen.

I think it deserves some more thorough testing. And if it is fixed, maybe we can find the commit that fixed it and the reason it was broken in case it turns up again in the future.

If you could help with that it would be great, as my bandwidth is quite limited.

Yarkhan commented 4 years ago

Seems it's finally gone. Tested on 0.61.5 and it is working fine

yarikpwnzer commented 4 years ago

Just tested on 0.62.0-rc.2 works perfect!!! TextInput now not freeze UI and onChangeText() setState() not lagging anymore, not need to make workaround like: this.inputRef.current._lastNativeText;

kelset commented 4 years ago

Cool - thanks everyone. When 0.62.0 is out I'll close this then.

diegolmello commented 4 years ago

@kelset Are you sure? I thought this was an architectural issue as mentioned by @axe-fb here: https://youtu.be/83ffAY-CmL4?t=1366

pke commented 4 years ago

On 0.62 I am still experiencing the issue on real low end devices. In the emulator its fast. Is there a way I can provide more debugging info?

fabOnReact commented 4 years ago

@kelset Are you sure? I thought this was an architectural issue as mentioned by @axe-fb here: https://youtu.be/83ffAY-CmL4?t=1366

This is the current problem (slides from infinite-red video and the solution is from @nparashuram)

You type R and you set value of R to the field.

image

You add E to R (RE) and another call to onChangeText is triggered, at the same time callback setText('R') is called

image

and now setText('RE') is called again while the user keeps updating that field and adding callback that are slowing down the app

image

WORKAROUND from @nparashuram

Add a debounce function, that delays calling onChangeText every 100 milleseconds

image

SOLUTION from @nparashuram

The solution that he proposes at minutes 24:55 (for the new react-native versions) is to use syncronous calls instead of asyncronous.

You type R --> syncronous call setValue('R') --> you type E --> syncronous call setValue('RE')

image

image

https://formidable.com/blog/2019/fabric-turbomodules-part-3/

wmonecke commented 4 years ago

I am experiencing this issue on RN 0.63.2 and it appears to be exactly what @fabriziobertoglio1987 is pointing to. If I remove value then TextInput is super fast. Many users on Android 8/9 phones (a little old, but not walkie talkies) are reporting that it is buggy to use the TextInput.

I am using a workaround where I remove the value prop. I set a defaultValue and use onChangeText to listen to changes. This stops the bugginess since it doesn't have to make the round trip explained above.

Even in the react-native docs they mention this flicker (I guess) https://reactnative.dev/docs/textinput#value

value: The value to show for the text input. TextInput is a controlled component, which means the native value will be forced to match this value prop if provided. For most uses, this works great, but in some cases this may cause flickering

Has anyone had bad experiences from using defaultValue and onChangeText?

laurent22 commented 4 years ago

@wmonecke, we are considering using your technique. Did you find any drawbacks to it eventually?

bdokimakis commented 3 years ago

I found a way to make the delay go away, without workarounds that avoid using the TextInput value or debouncing onChangeText() (and I'm stressing it quite a bit, using a barcode scanner that creates 13 chars within 0.5s).

I've completely bypassed the SpannableStringBuilder and I don't know if that creates other problems (I don't see any styling going away, even with properties that seem to want to use spans (e.g. letterSpacing)). All I know is that the delay has gone away, I see no issue in my app and after looking into this for days, I'm too tired to care about anything else. If anyone wants to test it out, you can use this in your package.json:

"react-native": "github:bdokimakis/react-native.git#0.63-stable-with-fix-for-20119"

Any feedback would be appreciated.

voids commented 3 years ago

Text input is also slow on iOS because of the value. That's a very bad experience while using battery saving mode Can I get the native value temporarily without set value?

tarouboy commented 3 years ago

We didn't set value on multi-line textinput for years, it was, well... "okay". Until lately after upgrading to 0.63, we started getting reports from users that after editing a while the whole phone became laggy and they have to restart the phone to go back normal.

We're still investigating and I couldn't reproduce the problem on my android devices as well.

wmonecke commented 3 years ago

For anyone having doubts about my workaround:

I have been using my solution posted above in my app with 40k MAU and I haven't seen a single report or issue.

Happy coding!

tarouboy commented 3 years ago

I am still receiving reports on this issue (without setting the value attribute). If anyone could help fixing this issue or building a standalone simplified textInput module ( maybe like this one? ) please PM.

HadiModarres commented 3 years ago

I'm encountering this issue on RN 0.63.2, TextInput becomes extremely laggy after a while if applying any of the following to the component

fabOnReact commented 3 years ago

the solution for this issue is create a method that uses the RNBridge syncronously instead asyncronously as explained in https://github.com/facebook/react-native/issues/20119#issuecomment-639482512

not worth it creating a pr as the react native team is working on the new architecture which should solve all the limitation from the react native bridge

HadiModarres commented 3 years ago

the solution for this issue is create a method that uses the RNBridge syncronously instead asyncronously as explained in #20119 (comment)

not worth it creating a pr as the react native team is working on the new architecture which should solve all the limitation from the react native bridge

@fabriziobertoglio1987 This didn't resolve the issue for me

tarouboy commented 3 years ago

the solution for this issue is create a method that uses the RNBridge syncronously instead asyncronously as explained in #20119 (comment)

not worth it creating a pr as the react native team is working on the new architecture which should solve all the limitation from the react native bridge

As I mentioned, I did not set the value attribute and also with a debounced onChange ( so no async callback I suppose? ), but the performance drop remains. I have received more than 10 reports mentioning it will cause the keyboard lag with serious delay, even after quitting the app. They will have to restart the device to back to normal.

For me, I could easily reproduce the performance drop even with flagship devices by pasting around 15-20k characters into the textInput. Maybe it's a rare case in most the app but for a diary or note-taking app it's quite normal.

If anyone could provide any workaround or a temp fix, I am willing to donate.

Thanks.

HadiModarres commented 3 years ago

the solution for this issue is create a method that uses the RNBridge syncronously instead asyncronously as explained in #20119 (comment) not worth it creating a pr as the react native team is working on the new architecture which should solve all the limitation from the react native bridge

As I mentioned, I did not set the value attribute and also with a debounced onChange ( so no async callback I suppose? ), but the performance drop remains. I have received more than 10 reports mentioning it will cause the keyboard lag with serious delay, even after quitting the app. They will have to restart the device to back to normal.

For me, I could easily reproduce the performance drop even with flagship devices by pasting around 15-20k characters into the textInput. Maybe it's a rare case in most the app but for a diary or note-taking app it's quite normal.

If anyone could provide any workaround or a temp fix, I am willing to donate.

Thanks.

Same experience here, and I don't need to put that many characters in, putting in a few sentences and removing it by holding the backspace button a few times does it on iPhone X.

cweilguny commented 3 years ago

In 2018 (I think it was RN 0.52 or some version around that) we could fix it completely by uninstalling the Gboard app. That's the native google keyboard, which, I guess, is installed on every Android device. Then the lags are gone. So if it's no publicly available app, like some kind of corporate app where you have access and control over all devices, that's at least an option. Just be sure to install some other keyboard app like swift, or have at least a hardware keyboard at hand, before you uninstall/disable it. Else you are left with the voice keyboard (which we also uninstalled because for our app only hardware keyboards were used and the voice keyboard confused our users).

tarouboy commented 3 years ago

I found a way to make the delay go away, without workarounds that avoid using the TextInput value or debouncing onChangeText() (and I'm stressing it quite a bit, using a barcode scanner that creates 13 chars within 0.5s).

I've completely bypassed the SpannableStringBuilder and I don't know if that creates other problems (I don't see any styling going away, even with properties that seem to want to use spans (e.g. letterSpacing)). All I know is that the delay has gone away, I see no issue in my app and after looking into this for days, I'm too tired to care about anything else. If anyone wants to test it out, you can use this in your package.json:

"react-native": "github:bdokimakis/react-native.git#0.63-stable-with-fix-for-20119"

Any feedback would be appreciated.

Thank you @bdokimakis I can confirm that in my case ( https://github.com/facebook/react-native/issues/20119#issuecomment-777945896 ) removing the SpannableStringBuilder works pretty well, the lag is totally gone even with 100k+ characters. However there are still some text selection / typing problems to solve. If anyone could help investigate in this way it might be really helpful.

bdokimakis commented 3 years ago

Here's my mod in a package https://www.npmjs.com/package/react-native-no-lag-text-input for easier testing.

@tarouboy if you could let me know what text selection / typing problems you're facing, I will take a look.

HadiModarres commented 3 years ago

Here's my mod in a package https://www.npmjs.com/package/react-native-no-lag-text-input for easier testing.

@tarouboy if you could let me know what text selection / typing problems you're facing, I will take a look.

I tried this, but still have the issue.

bdokimakis commented 3 years ago

Here's my mod in a package https://www.npmjs.com/package/react-native-no-lag-text-input for easier testing. @tarouboy if you could let me know what text selection / typing problems you're facing, I will take a look.

I tried this, but still have the issue.

@HadiModarres try v.1.1.0 please and let me know exactly what issue you're having, if still there. Also, please confirm that you're checking text inputs where you've replaced RN's TextInput with NoLagTextInput that you've imported from my package.

HadiModarres commented 3 years ago

@bdokimakis Your component is behaving the same as the original TextInput for me. I used your component like:

<NoLagTextInput defaultValue={'hello'} />

And then typed a few sentences, and removed it by holding down the backspace key. The issue is that the component becomes slower and slower to enter each character and to delete them. Note that the issue isn't about flickering or anything like that, it seems like there's something weird going on in the component like some sort of leak or accumulating callbacks. Only way this slowdown doesn't happen is to not use any of defaultValue, value, or onChangeText, which makes the text input useless at that point.

bdokimakis commented 3 years ago

@HadiModarres thanks for testing. I just tried to reproduce the issue you described, but it seems to be working fine.

package.json: "react-native-no-lag-text-input": "^1.1.0" and "react-native": "0.63.3" code: image test result: https://user-images.githubusercontent.com/12087618/108032499-405ab580-703b-11eb-8ec6-d31d4d683a69.mp4

Let me know if you're testing in a different manner so I can try to reproduce.

davidvasquezr commented 3 years ago

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 commented 2 years ago

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 commented 2 years ago

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[bot] commented 1 year 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.

gnprice commented 1 year ago

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

RemyLivewall commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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

I took a few approaches:

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

fabOnReact commented 10 months ago

See more info in this comment https://github.com/facebook/react-native/issues/20119#issuecomment-639482512 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:

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

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

2) JS TextInput sends "Hello" to Java EditText 3) Java EditText shows Hello in the TextInput 4) Java notifies JS TextInput that Hello was added on the EditText 5) 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 commented 9 months ago

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 commented 3 months 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.

gnprice commented 3 months ago

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