-
Notifications
You must be signed in to change notification settings - Fork 24.4k
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
Wrong button position when using KeyboardAvoidingView in combination with SafeAreaView and autofocus #29499
Comments
Possibly related to #29467 but the reverted changes which are mentioned there do not help. |
Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may also label this issue as a "Discussion" or add it to the "Backlog" and I will leave it open. Thank you for your contributions. |
I'm on react-native 0.63.3 and still face this issue |
I experience the same issue. I can say that I see it on iOS only. It does not appear on most of the devices that I've tried. It appears on iPhone Xr. Removing the |
this is a really common use case. are there any workaround people have found other than removing |
Sort-of workaround is to wrap the TextInput component and focus the input manually (based on autofocus prop) with a little timeout. This breaks the screen transition animation if navigating (with react-navigation) to the next screen which also has |
Can confirm this bug still exists. Manually focusing requires too much delay sadly. |
+1 In my case it's because of react-navigation. My workaround: // With ref
const insets = useSafeAreaInsets();
const headerHeight = useHeaderHeight();
const ref = useRef(null);
useEffect(() => {
const unsubscribe = navigation.addListener('transitionEnd', () => {
ref.current?.focus();
});
return unsubscribe;
}, [navigation]);
return (
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
keyboardVerticalOffset={headerHeight + insets.bottom}>
<View>
<TextInput ref={ref} />
</View>
</KeyboardAvoidingView>
)
// Without ref (In case you can't set ref)
const insets = useSafeAreaInsets();
const headerHeight = useHeaderHeight();
const [isNavMounted, setNavMounted] = useState(false);
useEffect(() => {
const unsubscribe = navigation.addListener('transitionEnd', () => {
setNavMounted(true);
});
return unsubscribe;
}, [navigation]);
return (
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
keyboardVerticalOffset={headerHeight + insets.bottom}>
<View>
{isNavMounted && <Component autoFocus/>}
</View>
</KeyboardAvoidingView>
) |
Thank you, works like a charm! |
Refactored the above to the following 2 hooks: Hook to return transition complete based on react navigation // Returns finished as true when transition is complete
const useScreenTransitionEnded = () => {
const navigation = useNavigation();
const [finished, setTransitionFinished] = useState(false);
useEffect(() => {
const unsubscribe = navigation.addListener('transitionEnd', () => {
setTransitionFinished(true);
});
return unsubscribe;
}, [navigation]);
return {finished};
}; Hook to focus an input when the above returns finished // focuses an input ref when transition is completed
const useInputFocusAfterTransition = inputRef => {
const {finished} = useScreenTransitionEnded();
useEffect(() => {
if (finished && inputRef.current) {
inputRef.current?.focus();
}
}, [finished]);
}; Usage const nameRef = useRef();
useInputFocusAfterTransition(nameRef);
...
<TextInput ref={nameRef} />
Just pass the hook the input ref, it will handle all the logic and autofocus when a transition is done Thanks @oliverfrat |
Still face the issue on react-native 0.66.3 |
Still face the issue on react-native 0.68.2 Here is the best way (from my side) to handle this issue: export const useScreenTransitionEnded = () => {
const navigation = useNavigation();
const [isTransitionFinished, setTransitionFinished] = React.useState(false);
React.useEffect(() => {
const unsubscribe = navigation.addListener("transitionEnd", () => {
setTransitionFinished(true);
});
return unsubscribe;
}, [navigation]);
return isTransitionFinished;
};
export const useKeyboardVerticalOffset = (offset = 0) => {
const isTransitionFinished = useScreenTransitionEnded();
const inset = useSafeAreaInsets();
return isTransitionFinished
? offset + (inset.top + inset.bottom)
: offset;
}; Usage: const Component = () => {
const keyboardVerticalOffset = useKeyboardVerticalOffset(64);
return (
<KeyboardAvoidingView keyboardVerticalOffset={keyboardVerticalOffset}>
{/* ... */}
</KeyboardAvoidingView>
)
} I've tried Thank you guys |
This is still a (common?) issue with seemingly no good workaround. Could we have some dev input? |
While the useEffect with the transitionEnded listener works, its a bit too large for my liking. I managed to solve this issue by firing the focus function using the onLayout prop. Also this error only happened with "email-address" keyboard type text inputs in my case. <TextInput
onLayout={() => emailInputRef.current?.focus()}
textContentType="emailAddress"
keyboardType="email-address"
autoCorrect={false}
autoCapitalize="none"
ref={emailInputRef}
/> Bonus: the focus happens a lot faster when navigating, giving you the behavior you were looking for. |
@qardpeet great that you've found an even better workaround! Happy to see even better solutions :) |
react-navigation/react-navigation#10681 same issue. I think it is caused by react-navigation, the frame height will be changed twice. |
…ed (#36970) Summary: Fix this issue: #29499 - We should change the bottom height if the frame height of KeyboardAvoidingView is changed In some scenarios, the height of `KeyboardAvoidingView` would be changed because its container is re-layouted. So `onLayout` of `KeyboardAvoidingView` may be triggered more than once. https://github.com/facebook/react-native/blob/bbc3657ff4efd0218e02ad9a3c73725a7f8a366c/packages/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js#L114-L125 But at line 122 above, `_updateBottomIfNecessary ` would be called only for the first trigger of `onLayout`. That means, if the height of `KeyboardAvoidingView` is changed, the bottom height can't be updated. #### See the videos below: ##### before In this video, `KeyboardAvoidingView ` is rendered twice, and the height is changed from 844 to 753, `bottomHeight ` is not updated when the height changed, so there is a white gap below the `continue` button. Once I re-open the keyboard, `_updateBottomIfNecessary` is called again, then the white gap disappeared. https://user-images.githubusercontent.com/25719782/232962924-c69adc11-deb9-4426-9b5c-4e990a0470db.mp4 ##### after https://user-images.githubusercontent.com/25719782/232962956-a163020f-5f40-4d82-9f6c-5ee67416c489.mp4 ## Changelog: [GENERAL] [CHANGED] - change `_onLayout` to update bottom height when frame height is changed Pull Request resolved: #36970 Reviewed By: rshest Differential Revision: D45138176 Pulled By: NickGerleman fbshipit-source-id: b7ce6d75622ed6e8f104ae0d8441e1cb97cfa15b
…ed (facebook#36970) Summary: Fix this issue: facebook#29499 - We should change the bottom height if the frame height of KeyboardAvoidingView is changed In some scenarios, the height of `KeyboardAvoidingView` would be changed because its container is re-layouted. So `onLayout` of `KeyboardAvoidingView` may be triggered more than once. https://github.com/facebook/react-native/blob/bbc3657ff4efd0218e02ad9a3c73725a7f8a366c/packages/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js#L114-L125 But at line 122 above, `_updateBottomIfNecessary ` would be called only for the first trigger of `onLayout`. That means, if the height of `KeyboardAvoidingView` is changed, the bottom height can't be updated. #### See the videos below: ##### before In this video, `KeyboardAvoidingView ` is rendered twice, and the height is changed from 844 to 753, `bottomHeight ` is not updated when the height changed, so there is a white gap below the `continue` button. Once I re-open the keyboard, `_updateBottomIfNecessary` is called again, then the white gap disappeared. https://user-images.githubusercontent.com/25719782/232962924-c69adc11-deb9-4426-9b5c-4e990a0470db.mp4 ##### after https://user-images.githubusercontent.com/25719782/232962956-a163020f-5f40-4d82-9f6c-5ee67416c489.mp4 ## Changelog: [GENERAL] [CHANGED] - change `_onLayout` to update bottom height when frame height is changed Pull Request resolved: facebook#36970 Reviewed By: rshest Differential Revision: D45138176 Pulled By: NickGerleman fbshipit-source-id: b7ce6d75622ed6e8f104ae0d8441e1cb97cfa15b
…ed (facebook#36970) Summary: Fix this issue: facebook#29499 - We should change the bottom height if the frame height of KeyboardAvoidingView is changed In some scenarios, the height of `KeyboardAvoidingView` would be changed because its container is re-layouted. So `onLayout` of `KeyboardAvoidingView` may be triggered more than once. https://github.com/facebook/react-native/blob/bbc3657ff4efd0218e02ad9a3c73725a7f8a366c/packages/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js#L114-L125 But at line 122 above, `_updateBottomIfNecessary ` would be called only for the first trigger of `onLayout`. That means, if the height of `KeyboardAvoidingView` is changed, the bottom height can't be updated. #### See the videos below: ##### before In this video, `KeyboardAvoidingView ` is rendered twice, and the height is changed from 844 to 753, `bottomHeight ` is not updated when the height changed, so there is a white gap below the `continue` button. Once I re-open the keyboard, `_updateBottomIfNecessary` is called again, then the white gap disappeared. https://user-images.githubusercontent.com/25719782/232962924-c69adc11-deb9-4426-9b5c-4e990a0470db.mp4 ##### after https://user-images.githubusercontent.com/25719782/232962956-a163020f-5f40-4d82-9f6c-5ee67416c489.mp4 ## Changelog: [GENERAL] [CHANGED] - change `_onLayout` to update bottom height when frame height is changed Pull Request resolved: facebook#36970 Reviewed By: rshest Differential Revision: D45138176 Pulled By: NickGerleman fbshipit-source-id: b7ce6d75622ed6e8f104ae0d8441e1cb97cfa15b
This does not consistent work from my testing. Looks like the react-navigation underlying issue makes the other workaround that uses ontransition end a more reliable solution. |
Hey guys, in my specific case, I was able to fix it by using Note 1: It is an workaround, not a proper fix Beforeimport { Redirect, usePathname } from 'expo-router'
// ...
const currentRoute = usePathname()
// ...
if (user && currentRoute !== '/name') {
if (!user.name) return <Redirect href="/name" />
return <Redirect href="/(app)" />
} Afterimport { router, usePathname } from 'expo-router'
// ...
const currentRoute = usePathname()
// ...
if (user && currentRoute !== '/name') {
if (!user.name) return router.push('/name')
return router.push('/app')
} |
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. |
@SimonVillage Can you remove the Stale label? As far as I know, this is still an issue |
I was having the same problem. Using onLayout seemed to work for the most part, but I did experience the inconsistent behavior that @Maker-Mark was talking about, so I sought to investigate the cause behind the matter. For iOS, the solution for me was removing padding from my KeyboardAvoidingView styles and putting it in a direct child View. The KeyboardAvoidingView's behavior is typically 'padding' on iOS, which probably applies some padding on it already, so I guessed that the two could possibly be interfering, performed that fix, and it works in my app now. For Android, using behavior='padding' seemed to cause bad flickering and using behavior='height' ironically miscalculated the appropriate height for one Stack.Screen. So a couple of Stack Overflow posts told me that Android doesn't really need to have a KeyboardAvoidingView. So you can just set behavior='undefined' for Android and it should function just like a regular View. Here's the new prop: behavior={Platform.OS === 'ios' ? 'padding' : undefined} // [Platform.select(...) works too]. In either case, there was no need to use keyboardVerticalOffset, which I'm very happy about. Screen sizes vary so much that I don't like its absolute nature. I haven't tested these exhaustively (only iPhone 12 and Pixel 3a simulator), but hopefully this helps. |
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. |
Please remove the stale label. As far as I know, this is still an issue. |
Can anyone create an updated repro with only the react-native core? ex: v0.76.3+ and with the new architecture enabled. |
Description
I have an input with autoFocus, a SafeAreaView, and a KeyboardAvoidingView. However the Button which should have his position exactly above the keyboard gets some margin when using SafeAreaView in combination with autoFocus.
It is to mention that if I add a delay to autoFocus of around 250ms it is working as expected.
I've build an expo snack here: https://snack.expo.io/@simbob/keyboardavoidingview-bug
React Native version:
Expected Results
Button should have the same position all time.
Steps To Reproduce
The text was updated successfully, but these errors were encountered: