Repository navigation
Closed
Labels
API: KeyboardComponent: KeyboardAvoidingViewComponent: ScrollViewNeeds: Triage 🔍Platform: iOSiOS applications.iOS applications.Resolution: LockedThis issue was locked by the bot.This issue was locked by the bot.
Description
Description
When a KeyboardAvoidingView is wrapped around a ScrollView with behavior="height" and the scrollview has contents that has a scrollable height > device height, focussing a text input will freeze app on ios, I cannot test on android at the moment so can only confirm the issue on iOS.
React Native version:
System:
OS: macOS 11.0.1
CPU: (8) x64 VirtualApple @ 2.50GHz processor
Memory: 119.02 MB / 16.00 GB
Shell: 5.8 - /bin/zsh
Binaries:
Node: 14.15.1 - /var/folders/wr/9smsws7s7fz53bs2905q080h0000gn/T/yarn--1606680389084-0.9742257659131075/node
Yarn: 1.22.10 - /var/folders/wr/9smsws7s7fz53bs2905q080h0000gn/T/yarn--1606680389084-0.9742257659131075/yarn
npm: 6.14.8 - ~/.nvm/versions/node/v14.15.1/bin/npm
Watchman: 4.9.0 - /usr/local/bin/watchman
Managers:
CocoaPods: 1.10.0 - /Users/oliverwinter/.rvm/gems/ruby-2.6.1/bin/pod
SDKs:
iOS SDK:
Platforms: iOS 14.2, DriverKit 20.0, macOS 11.0, tvOS 14.2, watchOS 7.1
Android SDK:
API Levels: 29, 30
Build Tools: 28.0.3, 29.0.2, 29.0.3, 30.0.2
System Images: android-30 | Google APIs Intel x86 Atom
Android NDK: Not Found
IDEs:
Android Studio: 4.1 AI-201.8743.12.41.6953283
Xcode: 12.2/12B45b - /usr/bin/xcodebuild
Languages:
Java: 1.8.0_275 - /usr/bin/javac
Python: 2.7.16 - /usr/bin/python
npmPackages:
@react-native-community/cli: Not Found
react: 17.0.1 => 17.0.1
react-native: 0.64.0-rc.0 => 0.64.0-rc.0
react-native-macos: Not Found
npmGlobalPackages:
*react-native*: Not Found
Steps To Reproduce
The following snippet can be used to replicate the issue.
import React from 'react';
import {
KeyboardAvoidingView,
SafeAreaView,
ScrollView,
TextInput,
View,
} from 'react-native';
const App = () => {
return (
<SafeAreaView>
<KeyboardAvoidingView behavior="height">
<ScrollView>
<TextInput
placeholder="Placeholder text"
style={{height: 50, fontSize: 20}}
/>
<View style={{height: 2000, backgroundColor: 'red'}} />
</ScrollView>
</KeyboardAvoidingView>
</SafeAreaView>
);
};
export default App;
Reacted by Ashoat Tevosyan, Chris A and Tim Traversy
Metadata
Metadata
Assignees
Labels
API: KeyboardComponent: KeyboardAvoidingViewComponent: ScrollViewNeeds: Triage 🔍Platform: iOSiOS applications.iOS applications.Resolution: LockedThis issue was locked by the bot.This issue was locked by the bot.
Activity
alloy commentedon Dec 7, 2020
@owinter86 Have you learned more about this issue by now, by any chance?
oliverwinter-vdb commentedon Dec 7, 2020
Hey @alloy unfortunately I have not learnt more about the issue, but its related to some infinite callback loop generated on the native side, and is specific to
<KeyboardAvoidingView behavior="height">as passing and position behaviour works fine.oliverwinter-vdb commentedon Dec 7, 2020
@alloy I do have some time now to look a bit more into it and find the commit that causes the issue, but at the moment this is what is causing the infinite loop in the Component
https://github.com/facebook/react-native/blob/b08fff6f869e00c20c0dcdf7aca71284c2f276f0/Libraries/Components/Keyboard/KeyboardAvoidingView.js#L109-L133
oliverwinter-vdb commentedon Dec 7, 2020
@alloy the issue was introduced in this commit b08fff6,
The issue is that onLayout event is fired multiple times and the
layout.yvalue for the native event is different each time, which then flows into the setState for height getting updated multiple times, with different values, which then trigger the onLayout again..... infinite loop.So the issue seems to stem from the onLayout events, and I am not sure if we can revert this commit in 64 due to the
Fabric has different order of eventscomment in the commit message.alloy commentedon Dec 7, 2020
Thanks so much! We'll discuss what to do about this 👍
Ashoat commentedon Dec 10, 2020
What's confusing me about b08fff6 is this line:
From my experimenting, the order of these events in React Native 0.63 is:
onLayoutis triggered, before anyTextInputis pressedTextInputis pressed a keyboard event is triggered. This keyboard event results in asetStatecallsetStatecall results in a re-render ofKeyboardAvoidingView, which results inonLayoutbeing called again(The issue we're seeing both here and in #30532 comes from the fact that there is now a
setStatecall inonLayout, which triggers another layout, which triggers anothersetState, etc.)But back to this quote:
My main question here is: after the first layout, assuming the parent component doesn't rerender, shouldn't a keyboard event always precede the
onLayout? In what circumstances does aonLayoutprecede a keyboard event? TheonLayoutcan't happen before thesetState, right? If anonLayoutoccurs without a preceding keyboard event, can't we conclude that it has nothing to do with the keyboard, and instead must be in response to a parent component resizing?If b08fff6 was indeed related to parent components resizing, I think I might have a solution.
I have a customized
KeyboardAvoidingViewin my project that has to deal with the edge case of a parent component resizing. We need to be able to differentiate between anonLayoutthat occurs in response to our ownsetState(in which case we should not change our calculations), and anonLayoutthat occurs in response to a parent resize (in which case the calculations need to be updated). The way we handle this is simply to ignore allonLayoutcalls that occurs while the keyboard is up (eg. after a "keyboard show" event occurs, but before a "keyboard hide" event occurs).I think that sort of approach would work here, but it's hard to say without understanding the issue that b08fff6 is addressing better.
kelset commentedon Dec 14, 2020
Let's cc in the author of the commit, maybe they'll be able to give more context: @sammy-SC
sammy-SC commentedon Dec 14, 2020
Hello @kelset, the fix is waiting for a review.
Hello @Ashoat
This is true unless TextInput has autofocus enabled. With autofocus enabled the order of events is different in Paper (old React native renderer) vs Fabric.
I hope this makes sense as I believe all of your other questions stem from the assumption that TextInput is tapped.
Apologies for the trouble.
kelset commentedon Dec 14, 2020
hey @sammy-SC, that's great to hear! I imagine it's an internal fix so there's no PR we can subscribe for updates to; can you post here the link to the commit on master after it lands? 🤗
sammy-SC commentedon Dec 14, 2020
@kelset yes, it is an internal fix. I will post the link here as soon as it is in master.
Ashoat commentedon Dec 14, 2020
Thanks for the explanation @sammy-SC!
I actually spent a good amount of time last month specifically looking into
autoFocus/KeyboardAvoidingViewbehavior. Figured I'd share my notes here in case it's useful.My (contrasting) experience
I think my experience with React Native 0.63 (using Paper) is a little different. I've found that
TextInputwithautoFocus={true}will sometimes fire the keyboard event before theonLayoutevent. The result is that theonLayoutgets the wrong value for_initialFrameHeight(since it's already adjusted for the keyboard).My "fix" (hack)
My fix has been simply to avoid
autoFocusin these scenarios. In fact I've found that immediately callingfocus()in a ref can also have the same issue. I've been able to avoid the issue withrequestAnimationFrame(see experiment results below).A separate issue I've had with
autoFocus={true}when interacting with a keyboard has to do with theAnimatedlibrary. I've found that it can lead to fatal errors. In particular I've experienced this issue with React Navigation modals.Last month I did some experimenting to see what the best approach would be for "autofocusing
TextInputinside React Navigation modal". Figured I'd share my results here:Experiment results
Notes:
Animatedissue (native exception) only repros in dev mode. I wasn’t able to reproduce it in prod modeautoFocussetTimeout(50)setTimeout(0)waitForInteractionsrequestAnimationFrameuseEffectequivalentExperiment analysis
Based on test results, the three leading candidates are
setTimeout(0),requestAnimationFrame, anduseEffect. Some thoughts:Animatedlibrary. This makes me lean a little towardsrequestAnimationFrame, which the React Native docs say "will fire after all the frame has flushed".Long-term fix
If the issue in
Animatedis addressed, and we have a consistent order of events forautoFocus={true}, I think we could updateKeyboardAvoidingViewto handle this use case.The thinking would be that if a keyboard event fires before an
onLayout, then when setting_initialFrameHeighton the initialonLayout, we would adjust it based on the measurements in the keyboard event.Not sure if this is what the aforementioned internal fix does.
sammy-SC commentedon Dec 15, 2020
Here is the fix: 6730927
@Ashoat that's amazing! Thank you, that's definitely useful.
Fix infinite loop in KeyboardAvoidingView
Merge from upstream 0.64-stable to v0.64.0 (#863)