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

KeyboardAvoidingView mistakingly adds white space on screen bottom after being untangled #29614

Open
AndyYuanZhou opened this issue Aug 11, 2020 · 56 comments
Labels
API: Keyboard Component: KeyboardAvoidingView Needs: Attention Issues where the author has responded to feedback.

Comments

@AndyYuanZhou
Copy link

AndyYuanZhou commented Aug 11, 2020

[Description]

I wrapped a view as a footer inside KeyboardAvoidingView. It works properly before the keyboard has been untoggled. However, after the keyboard is untoggled, it adds a white space on the bottom of the screen. The height of the white space is as big as the keyboardVerticalOffset. This white space shouldn't exist.

React Native version:

System:
OS: macOS 10.15.6
CPU: (8) x64 Intel(R) Core(TM) i7-7920HQ CPU @ 3.10GHz
Memory: 28.43 MB / 16.00 GB
Shell: 3.2.57 - /bin/bash
Binaries:
Node: 12.18.1 - /usr/local/bin/node
Yarn: Not Found
npm: 6.14.5 - /usr/local/bin/npm
Watchman: 4.9.0 - /usr/local/bin/watchman
Managers:
CocoaPods: 1.9.3 - /usr/local/bin/pod
SDKs:
iOS SDK:
Platforms: iOS 13.6, DriverKit 19.0, macOS 10.15, tvOS 13.4, watchOS 6.2
Android SDK: Not Found
IDEs:
Android Studio: Not Found
Xcode: 11.6/11E708 - /usr/bin/xcodebuild
Languages:
Java: 14.0.1 - /usr/bin/javac
Python: 2.7.16 - /usr/bin/python
npmPackages:
@react-native-community/cli: Not Found
react: ~16.11.0 => 16.11.0
react-native: https://github.com/expo/react-native/archive/sdk-38.0.1.tar.gz => 0.62.2
npmGlobalPackages:
react-native: Not Found

Steps To Reproduce

Provide a detailed list of steps that reproduce the issue.

  1. Use KeyboardAvoidingView, set keyboardVerticalOffset to any value
  2. Put a view inside KeyboardAvoidingView, set the position as absolute, and set the bottom to 0

Expected Results

The white space for keyboardVerticalOffset should only exist when the keyboard is toggled.

Snack, code example, screenshot, or link to a repository:

<KeyboardAvoidingView
      behavior={Platform.OS == "ios" ? "padding" : "height"}
      keyboardVerticalOffset={48}
      style={{width: "100%",
                   height: "100%",
                   position: "absolute",
                   zIndex: 1,}}
>
      <View
              style={{width: "100%",
                            position: "absolute",
                            height: 56,
                            bottom: 0,}}
      >
              <SubmitButton/>
       </View>
</KeyboardAvoidingView>

Artboard

@chrisglein
Copy link

This looks similar to the sample for KeyboardAvoidingView. We're having trouble reproducing your snippet in a Snack. Mind helping refine this so we can get the repro? Have this so far, which more like the docs sample has the page content as well as the button inside the KeyboardAvoidingView.

@AndyYuanZhou
Copy link
Author

Thanks for responding and creating this snack. I just tried what you created. Try to add this prop
keyboardVerticalOffset={100}
to KeyboardAvoidingView and run it on iOS. You'll see how the button is pushed up after the keyboard gets toggled then dismissed. It works properly in Android.

@github-actions github-actions bot added Needs: Attention Issues where the author has responded to feedback. and removed Needs: Author Feedback labels Aug 12, 2020
@TravellerOnTheRun
Copy link

hey, guys, the same issue here, did you solve it?

@keremcubuk
Copy link

I'm having same trouble. In this example, when my screen is fit and no need to scroll it works perfectly. But when my screen has scrollable content, it adds white spaces in every input focusing under the <Button> component.

Note: With padding IOS platform works perfect.

Multiple input with behavior: "height"

Multiple Input with behavior: "padding"

Only one Input with behavior: "height"

My example Code:

<KeyboardAvoidingView
  style={{ flex: 1 }}
  behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
  keyboardVerticalOffset={Platform.OS === 'ios' ? (isIphoneX() ? 88 : 64) : StatusBar.currentHeight + 50} // 50 is Button height
  enabled>
   <ScrollView contentContainerStyle={{ flexGrow: 1 }} keyboardShouldPersistTaps={'handled'}>
     <Input />
    </ScrollView>
    <Button
      onPress={() => {}}
      style={{ justifyContent: 'flex-end' }}
      text={"Confirm"}
    />
</KeyboardAvoidingView>

I need a help for general solution. 🚀

@kostiantyn-solianyk
Copy link

kostiantyn-solianyk commented Apr 5, 2021

@keremcubuk Did you find a solution?

@F3WS
Copy link

F3WS commented Aug 11, 2021

Here's my solution, I can't promise it will work for everyone and in all situations but it's working where it needs to in my current project, it involves replacing 'flex: 1' with the window height minus the status bar height and setting the behavior to position. Hope it helps.

<KeyboardAvoidingView
        behavior="position"
        style={{
          height: Platform.OS === 'android' ? Dimensions.get('window').height - StatusBar.currentHeight : '100%',
          alignItems: 'center',
        }}
      >
....
</KeyboardAvoidingView>

@tomo10
Copy link

tomo10 commented Oct 26, 2021

Thanks F3WS, but that solution not working for me. Anyone had any success using different workaround?

@Naua-stack
Copy link

any solution?

@annaostapenko22
Copy link

facing the same problem, did anyone find a solution?

@talaviad5
Copy link

Any solution?

@annaostapenko22
Copy link

annaostapenko22 commented Jan 26, 2022

In my case I added behavior={isAndroid ? 'height' : 'position'} and also needed to add different keyboardVerticalOffset for android, iPhoneX and normal iPhone

const calculateKeyboardVerticalOffset = () => {
  if (isAndroid) return StatusBar.currentHeight;
  if (isIphoneX()) return -100;
  return 0;
};

    <KeyboardAvoidingView
      behavior={isAndroid ? 'height' : 'position'}
      enabled
      keyboardVerticalOffset={calculateKeyboardVerticalOffset()}
    >

Hope this helps @talaviad5

@talaviad5
Copy link

@annaostapenko22 still doesn't work, but thanks

@Digiraf
Copy link

Digiraf commented May 13, 2022

i fixed it too with

const additionHeight=Platform.OS=='ios'?0:StatusBar.currentHeight;

<KeyboardAvoidingView
behavior={(Platform.OS === 'ios') ? "margin" :'padding'}
contentContainerStyle={{ flex: 1 }}
keyboardVerticalOffset={Platform.select({ios: 2, android: -additionHeight})}

@sajjad-yousaf
Copy link

behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
keyboardVerticalOffset={-900}

this will fix your issue

@anckaertv
Copy link

still have the issue, any solution ?

@nmateo
Copy link

nmateo commented Nov 11, 2022

Still not resolved.

@Digiraf
Copy link

Digiraf commented Nov 11, 2022

import {
KeyboardAvoidingView,SafeAreaView,TouchableWithoutFeedback,....
} from 'react-native';
<KeyboardAvoidingView
                style={{ flex: 1 }}
                behavior={(Platform.OS === 'ios') ? "padding" : null}
                keyboardVerticalOffset={Platform.select({ios: 80, android: 500})} enabled>
                <SafeAreaView style={styles.container}>
                    <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
                      <View style={{flex:1,padding:20}}>
                <TextInput maxLength={50} value={this.getVal('name')} onChangeText={(txt)=>{
                  ....
                }}

                     </View>
                    </TouchableWithoutFeedback>
                </SafeAreaView>

            </KeyboardAvoidingView>

Edit your AndroidManifest.xml

 <activity
                android:name=".MainActivity"
                android:label="@string/app_name"
                android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
                android:launchMode="singleTask"
            android:screenOrientation="landscape"
                android:windowSoftInputMode="adjustResize"
            android:exported="true"
            >

this will do the job

@arthwood
Copy link

I had same issue and my style for KAV was:

flex: 1

Inner ScrollView had flex: 1 too.
Just changing KAV style to flexGrow: 1 fixed the issue.

@sLurPPPeRsTaR
Copy link

i replace my keyboardavoidingview with

https://github.com/APSL/react-native-keyboard-aware-scroll-view

and solved.

@mmkhmk
Copy link

mmkhmk commented Jan 20, 2023

I'm facing the same issue.

@Rc85
Copy link

Rc85 commented Feb 1, 2023

I'm facing the same issue only on iOS. It adds a lot of whitespace at the bottom of a ScrollView and every time I blur and focus on the input again, it adds even more whitespace.

I think this is a major bug that needs to be addressed. The whitespace needs to be removed after the keyboard hides.

@abumubaarak
Copy link

Try to set the behavior on iOS as "height"

@Gogoro
Copy link

Gogoro commented May 7, 2023

I had same issue and my style for KAV was:

flex: 1

Inner ScrollView had flex: 1 too. Just changing KAV style to flexGrow: 1 fixed the issue.

This fixed it for me, and it's so much nicer than adding some extra library 🔥 Thanks!

image

@alpalla
Copy link

alpalla commented May 29, 2023

I faced a similar issue and I'll share my solution here in case this helps anyone.

Context

Note: this only happened in iOS (not Android).

In my case, when the keyboard was visible and I clicked a CTA to go to the next screen, I would get the extra white space (equal to the KeyboardAvoidingView's keyboardVerticalOffset) at the bottom of the next screen.

Clicking on the CTA dismissed the keyboard, however while the keyboard was still being dismissed the navigation to the next page started (calling push from solito/router). Basically, the user arrived at the next screen while the keyboard was not fully dismissed. Therefore it seems the KeyboardAvoidingView kept applying the keyboardVerticalOffset even though the keyboard was eventually dismissed.

In fact, waiting for the keyboard to be dismissed before navigating to the next screen solved the issue. As did wrapping the call to router.push in a setTimeout (with even just a 50ms delay).

Solution

The main idea is to disable the KeyboardAvoidingView as soon as we know the keyboard will be hidden:

export const MyKeyboardAvoidingView = ({ behavior, children }: Props) => {
	const headerHeight = useHeaderHeight()

	const [enabled, setEnabled] = useState(false)

	useEffect(() => {
		const showSubscription = Keyboard.addListener('keyboardWillShow', () => {
			setEnabled(true)
		})

		const hideSubscription = Keyboard.addListener('keyboardWillHide', () => {
			setEnabled(false)
		})

		return () => {
			showSubscription.remove()
			hideSubscription.remove()
		}
	}, [])

	return (
		<KeyboardAvoidingView
			behavior={behavior}
			enabled={enabled}
			keyboardVerticalOffset={headerHeight}
		>
			{children}
		</KeyboardAvoidingView>
	)
}

@horlorlahdeh
Copy link

I had same issue and my style for KAV was:

flex: 1

Inner ScrollView had flex: 1 too. Just changing KAV style to flexGrow: 1 fixed the issue.

Thank you! This solution works perfectly for me @arthwood

@zorzpapaduby
Copy link

zorzpapaduby commented Jul 4, 2023

@taylorkangbeck I have the same issue, and none of the solutions work for me. Did you find out how we can overcome this?

edit: I am making a chat app, so doesn't help with this neither.

@MedRaid
Copy link

MedRaid commented Sep 5, 2023

for me the problem was only android side, so what i actually did is this and i hope it helps someone maybe

KeyboardAvoidingView
        style={{flex: 1, flexDirection: 'column', justifyContent: 'center'}}
        behavior="padding"
        enabled
        keyboardVerticalOffset={Platform.select({
          ios: () => 0,
          android: () => -300,
        })()}>

don't forget to import Platform using

import { KeyboardAvoidingView, Platform } from 'react-native';

@stephenlyo
Copy link

stephenlyo commented Sep 13, 2023

Can't believe this issue was happening in 3 year ago, and then I came across here today. alpalla's solution works but I have some CSS change,

<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={twflex-1}
enabled={enabled}
keyboardVerticalOffset={useBottomTabBarHeight() + useHeaderHeight()}>
<ScrollView style={twm-2 ${Platform.OS === "ios" ? "h-full" : "h-300"}} automaticallyAdjustContentInsets={false}>

@ianfelix
Copy link

If you are having this issue on ios today, my solution was applying @arthwood solution with some changes. I am not using a scroll view, just a simple view and a TouchableWithoutFeedback

 <KeyboardAvoidingView
      behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
      keyboardVerticalOffset={Platform.OS === 'ios' ? 80 : 0}
      style={{flex: 1}}>
   <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
    <View className="flex-1 grow "> // flexGrow: 1 and flex-1

@anagovitsyn
Copy link

I had same issue and my style for KAV was:

flex: 1

Inner ScrollView had flex: 1 too. Just changing KAV style to flexGrow: 1 fixed the issue.

Amazing, worked for me as well. So, why did it work, could someone explain?

@RomainLAU
Copy link

If you are having this issue on ios today, my solution was applying @arthwood solution with some changes. I am not using a scroll view, just a simple view and a TouchableWithoutFeedback

 <KeyboardAvoidingView
      behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
      keyboardVerticalOffset={Platform.OS === 'ios' ? 80 : 0}
      style={{flex: 1}}>
   <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
    <View className="flex-1 grow "> // flexGrow: 1 and flex-1

Hello ! I'm still having this issue but I can't remove the ScrollView component as I need my screen to be scrollable, anyone with a solution ?

@anagovitsyn
Copy link

If you are having this issue on ios today, my solution was applying @arthwood solution with some changes. I am not using a scroll view, just a simple view and a TouchableWithoutFeedback

 <KeyboardAvoidingView
      behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
      keyboardVerticalOffset={Platform.OS === 'ios' ? 80 : 0}
      style={{flex: 1}}>
   <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
    <View className="flex-1 grow "> // flexGrow: 1 and flex-1

Hello ! I'm still having this issue but I can't remove the ScrollView component as I need my screen to be scrollable, anyone with a solution ?

I finally gave up using KeyboardAvodingView as it seems to be very buggy and non-consistent across ios/android and use useKeyboard() from react-native-community/hooks and manually adjust all margins and paddings according to keyboardHeight or keyboardShown returned values along with keyboard object from useKeyboard()

@ianfelix
Copy link

If you are having this issue on ios today, my solution was applying @arthwood solution with some changes. I am not using a scroll view, just a simple view and a TouchableWithoutFeedback

 <KeyboardAvoidingView
      behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
      keyboardVerticalOffset={Platform.OS === 'ios' ? 80 : 0}
      style={{flex: 1}}>
   <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
    <View className="flex-1 grow "> // flexGrow: 1 and flex-1

Hello ! I'm still having this issue but I can't remove the ScrollView component as I need my screen to be scrollable, anyone with a solution ?

Another solution that worked for me on both Android and IOS was the code below.
When I need a screen with KeyboardAvoidingView I use this component. I notice that putting a view before the KeyboardAvoidingView the bug disappears

export const KeyBoardAvoidingViewLayout = ({
  header,
  children,
}: KeyBoardAvoidingViewLayoutProps) => {
  return (
    <View className="flex-1 bg-brand">
      {header}
      <KeyboardAvoidingView
        behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
        keyboardVerticalOffset={Platform.OS === 'ios' ? 60 : 0}
        className="mt-8 w-full flex-1 items-center rounded-t-2xl bg-white px-2">
        <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
          <View className="w-full flex-1">{children}</View>
        </TouchableWithoutFeedback>
      </KeyboardAvoidingView>
    </View>
  );
};

@RomainLAU
Copy link

If you are having this issue on ios today, my solution was applying @arthwood solution with some changes. I am not using a scroll view, just a simple view and a TouchableWithoutFeedback

 <KeyboardAvoidingView
      behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
      keyboardVerticalOffset={Platform.OS === 'ios' ? 80 : 0}
      style={{flex: 1}}>
   <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
    <View className="flex-1 grow "> // flexGrow: 1 and flex-1

Hello ! I'm still having this issue but I can't remove the ScrollView component as I need my screen to be scrollable, anyone with a solution ?

Another solution that worked for me on both Android and IOS was the code below. When I need a screen with KeyboardAvoidingView I use this component. I notice that putting a view before the KeyboardAvoidingView the bug disappears

export const KeyBoardAvoidingViewLayout = ({
  header,
  children,
}: KeyBoardAvoidingViewLayoutProps) => {
  return (
    <View className="flex-1 bg-brand">
      {header}
      <KeyboardAvoidingView
        behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
        keyboardVerticalOffset={Platform.OS === 'ios' ? 60 : 0}
        className="mt-8 w-full flex-1 items-center rounded-t-2xl bg-white px-2">
        <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
          <View className="w-full flex-1">{children}</View>
        </TouchableWithoutFeedback>
      </KeyboardAvoidingView>
    </View>
  );
};

It doesn't change anything for me, and it also cancels the KeyboardAvoidingView behavior (it doesn't push the content anymore when the keyboard is opened)

@ianfelix
Copy link

ianfelix commented Dec 2, 2023

If you are having this issue on ios today, my solution was applying @arthwood solution with some changes. I am not using a scroll view, just a simple view and a TouchableWithoutFeedback

 <KeyboardAvoidingView
      behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
      keyboardVerticalOffset={Platform.OS === 'ios' ? 80 : 0}
      style={{flex: 1}}>
   <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
    <View className="flex-1 grow "> // flexGrow: 1 and flex-1

Hello ! I'm still having this issue but I can't remove the ScrollView component as I need my screen to be scrollable, anyone with a solution ?

Another solution that worked for me on both Android and IOS was the code below. When I need a screen with KeyboardAvoidingView I use this component. I notice that putting a view before the KeyboardAvoidingView the bug disappears

export const KeyBoardAvoidingViewLayout = ({
  header,
  children,
}: KeyBoardAvoidingViewLayoutProps) => {
  return (
    <View className="flex-1 bg-brand">
      {header}
      <KeyboardAvoidingView
        behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
        keyboardVerticalOffset={Platform.OS === 'ios' ? 60 : 0}
        className="mt-8 w-full flex-1 items-center rounded-t-2xl bg-white px-2">
        <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
          <View className="w-full flex-1">{children}</View>
        </TouchableWithoutFeedback>
      </KeyboardAvoidingView>
    </View>
  );
};

It doesn't change anything for me, and it also cancels the KeyboardAvoidingView behavior (it doesn't push the content anymore when the keyboard is opened)

Please, put your code on a snack to see what's going on

@mengnans
Copy link

mengnans commented Mar 4, 2024

Can't believe this issue is still here, and it's 2024 now.

@Shubham0850
Copy link

Shubham0850 commented Mar 12, 2024

I can't believe that I'm still facing the same issue in the era of gpt 👀

@maroparo
Copy link

Facing the same issue, but only in the emulator 🤔 on a real device (expo server and local production build works fine...)

@Jad-Jbara
Copy link

Jad-Jbara commented Mar 29, 2024

I tried this approach and it worked for my case:

First I saved the keyboardVerticalOffset in a state and inside of KeyboardAvoidingView's onLayout, I switch between the value I set and 0 (One referring to keyboard focus and another for keyboard blur)

const KEYBOARD_OFFSET = 100

const KeyboardAvoidingBase: React.FC<KeyboardAvoidingViewProps> = ({
  children,
  style,
  ...props
}) => {
  const [offset, setOffset] = useState(0)

  const onLayout = () => {
    setOffset(currentOffset => currentOffset === KEYBOARD_OFFSET ? 0 : KEYBOARD_OFFSET)
  }

  return (
    <KeyboardAvoidingView
      testID='keyboard-avoiding-base'
      behavior='padding'
      onLayout={onLayout}
      keyboardVerticalOffset={offset}
      style={[styles.container, style]}
      {...props}>
      {children}
    </KeyboardAvoidingView>
  )
}

I haven't tested it on orientation change (I have the app set to portrait mode only), or on mounting new nodes in the render tree, so not sure if the onLayout would trigger on these changes, but this workaround worked for my case. Hope it works for you too. :)

EDIT:

I noticed a small issue which is causing the onLayout event to be triggered on first keystroke whenever the keyboard is focused. This was causing the padding to disappear if the keyboard is focused.

I did some changes and tested on a physical device (iPhone 11 pro max) and the below solution works like a charm.

import React, { useEffect, useRef, useState } from 'react'
import {
  Keyboard,
  KeyboardAvoidingView,
  KeyboardAvoidingViewProps,
} from 'react-native'

import DimensionHelper from 'Helpers/DimensionHelper'

import styles from './styles'

const KEYBOARD_OFFSET_SIZE = 100
const DISBALED_OFFSET = 0
const KEYBOARD_OFFSET = DimensionHelper.getHeight(KEYBOARD_OFFSET_SIZE)

const KeyboardAvoidingBase: React.FC<KeyboardAvoidingViewProps> = ({
  children,
  style,
  ...props
}) => {
  const [offset, setOffset] = useState(KEYBOARD_OFFSET)
  const lastUpdateRef = useRef('hide')

  useEffect(() => {
    const keyboardWillShowListener = Keyboard.addListener('keyboardWillShow', () => {
      if (lastUpdateRef.current === 'show') {
        return
      }
      lastUpdateRef.current = 'show'
      setOffset(DISBALED_OFFSET)
    })

    const keyboardWillHideListener = Keyboard.addListener('keyboardWillHide', () => {
      lastUpdateRef.current = 'hide'
      setOffset(KEYBOARD_OFFSET)
    })

    return () => {
      keyboardWillShowListener.remove()
      keyboardWillHideListener.remove()
    }
  }, [offset])

  return (
    <KeyboardAvoidingView
      testID='keyboard-avoiding-base'
      behavior={'padding'}
      keyboardVerticalOffset={offset}
      style={[styles.container, style]}
      {...props}>
      {children}
    </KeyboardAvoidingView>
  )
}
export default KeyboardAvoidingBase

NOTE: the above solution doesn't solve the issue on simulators, but it works for physical devices.

@FoRavel
Copy link

FoRavel commented Apr 30, 2024

On android I set "behavior" prop to "undefined". On IOS I set "behavior" to "padding". According to the RN documentation behavior is just required on IOS.

<KeyboardAvoidingView 
behavior={Platform.OS === 'ios' ? 'padding' : undefined}>
</KeyboardAvoidingView>

It solved the issue on Android for me.

EDIT : Apologize, RN Documentation contrary to what I said recommend to set behavior for both IOS and Android.

@Jad-Jbara
Copy link

@FoRavel The docs say it is recommended to set behavior for both platforms.
check the docs here

Android and iOS both interact with this prop differently. On both iOS and Android, setting behavior is recommended.

@abhaykumar181
Copy link

abhaykumar181 commented May 17, 2024

I solved like this. Hope it works for you guys.

const [keyboardStatus, setKeyboardStatus] = useState(1);
useEffect(() => {
    const showSubscription = Keyboard.addListener('keyboardDidShow', () => {
      setKeyboardStatus(1);
    });
    const hideSubscription = Keyboard.addListener('keyboardDidHide', () => {
      setKeyboardStatus(0);
    });
    return () => {
      showSubscription.remove();
      hideSubscription.remove();
    };
}, []);

<KeyboardAvoidingView 
            style={{ flex: 1, height:'100%' }}
            contentContainerStyle={{ flex: 1 }}
            behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
            enabled
            keyboardVerticalOffset={Platform.OS === 'ios' ? 88 : keyboardStatus === 1 ? 64 : 0} >
</KeyboardAvoidingView>

@enmanuelmag
Copy link

I solved like this. Hope it works for you guys.

const [keyboardStatus, setKeyboardStatus] = useState(1);
useEffect(() => {
    const showSubscription = Keyboard.addListener('keyboardDidShow', () => {
      setKeyboardStatus(1);
    });
    const hideSubscription = Keyboard.addListener('keyboardDidHide', () => {
      setKeyboardStatus(0);
    });
    return () => {
      showSubscription.remove();
      hideSubscription.remove();
    };
}, []);

<KeyboardAvoidingView 
            style={{ flex: 1, height:'100%' }}
            contentContainerStyle={{ flex: 1 }}
            behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
            enabled
            keyboardVerticalOffset={Platform.OS === 'ios' ? 88 : keyboardStatus === 1 ? 64 : 0} >
</KeyboardAvoidingView>

Thanks! This works, but a noticed that if a want to increase the gap between the top of the kayboard and the content I must change the keyboardVerticalOffset but this add a white space. I understand that is the expected behaviour but, Is there a work around in order of turn white space to transparent?

@abhaykumar181
Copy link

abhaykumar181 commented Jun 25, 2024

Hi @enmanuelmag,

Thanks for your feedback on my solution!

I also faced the same issue with the white space. As a workaround, I switched to using the react-native-keyboard-aware-scroll-view library, which provides more flexibility and avoids the problem with the white space.

I know this GitHub issue is regarding KeyboardAvoidingView, but I recommend switching to this package.

Here's an example of how you can implement it:

return (
        <View style={loginStyles.container}>
            {/* Status Bar */}
            <StatusBar
                barStyle={isDarkMode ? "light-content" : "dark-content"}
                hidden={false}
                backgroundColor={isDarkMode ? "#000" : "#fff"}
            />

            {/* App Header */}
            <Header disableButtons={!!(isButtonLocked)} />

            {/* Keyboard Aware Scroll */}
            <KeyboardAwareScrollView
                contentContainerStyle={{ flexGrow: 1 }}
                keyboardShouldPersistTaps={'handled'}
                enableOnAndroid={true}
                extraScrollHeight={Platform.select({ ios: 80, android: 40 })}
                extraHeight={180}
                keyboardOpeningTime={0}
                enableAutomaticScroll={Platform.OS === 'ios'}
                enableResetScrollToCoords={false} >
                
                <View style={loginStyles.loginFormBox}>
                    {/* Screen Title */}
                    <View style={loginStyles.screenTitleBox}>
                        <AbelProText textColor='gray' familyStyle={'bold'} size={selectedFont.size + 4}>USER LOGIN</AbelProText>
                    </View>

                    {/* Input Box */}
                    <View>
                        {errorStatus && (
                            <View style={loginStyles.alertBox_Error}>
                                <MaterialIcon name='error' color={'red'} size={selectedFont.size} style={loginStyles.formErrorIcon} />
                                <NunitoSansText textColor='red' style={loginStyles.messages} size={selectedFont.size}>{errorStatus}</NunitoSansText>
                            </View>
                        )}

                        {/* Email TextInput */}
                        <View>
                            <TextInput
                                inputMode="email"
                                placeholder="Enter Username"
                                style={[loginStyles.input, {
                                    borderWidth: formData.email.error ? 1 : 0,
                                    borderColor: "red",
                                    marginBottom: formData.email.error ? 10 : 20,
                                }]}
                                keyboardType="email-address"
                                autoComplete="off"
                                autoCorrect={false}
                                placeholderTextColor="#000"
                                ref={emailInput}
                                returnKeyType="next"
                                onSubmitEditing={() => passwordInput.current?.focus()}
                                blurOnSubmit={false}
                                onChangeText={onChangeEmail}
                                value={formData.email.value}
                                autoCapitalize="none"
                            />
                            {formData.email.error && (
                                <View style={loginStyles.formErrorBox}>
                                    <MaterialIcon name='error' color={'red'} size={selectedFont.size} style={loginStyles.formErrorIcon} />
                                    <NunitoSansText textColor='red' size={selectedFont.size - 2} style={loginStyles.formError}>{formData.email.error}</NunitoSansText>
                                </View>
                            )}
                        </View>
                        
                        {/* Password TextInput */}
                        <View>
                            <TextInput
                                inputMode="text"
                                placeholder="Password"
                                style={[loginStyles.input, {
                                    borderWidth: formData.password.error ? 1 : 0,
                                    borderColor: "red",
                                    marginBottom: formData.password.error ? 10 : 20,
                                }]}
                                keyboardType="default"
                                autoComplete="off"
                                autoCorrect={false}
                                placeholderTextColor="#000"
                                ref={passwordInput}
                                returnKeyType="next"
                                onSubmitEditing={Keyboard.dismiss}
                                blurOnSubmit={false}
                                onChangeText={onChangePassword}
                                value={formData.password.value}
                                secureTextEntry={true}
                                autoCapitalize="none"
                            />
                            {formData.password.error && (
                                <View style={loginStyles.formErrorBox}>
                                    <MaterialIcon name='error' color={'red'} size={selectedFont.size} style={loginStyles.formErrorIcon} />
                                    <NunitoSansText textColor='red' size={selectedFont.size - 2} style={loginStyles.formError}>{formData.password.error}</NunitoSansText>
                                </View>
                            )}
                        </View>
                    </View>

                    {/* Submit Button */}
                    <View>
                        <Pressable onPress={handleLogin} disabled={isButtonLocked}>
                            {({ pressed }) => (
                                <LinearGradient
                                    colors={!pressed ? ['rgb(0,177,205)', 'rgb(0,144,216)'] : ['rgb(0,144,216)', 'rgb(0,177,205)']}
                                    start={{ x: 0.0, y: 1.0 }} end={{ x: 1.0, y: 1.0 }}
                                    style={{ height: 45, justifyContent: "center", alignItems: "center" }}>
                                    
                                    <AbelProText
                                        style={{ textAlign: "center" }}
                                        familyStyle={'bold'} textColor={"#fff"} 
                                        size={selectedFont.size}>
                                        {isLoading ? <ActivityIndicator size={selectedFont.size + 4} color='#fff' /> : "SUBMIT"}
                                    </AbelProText>
                                </LinearGradient>
                            )}
                        </Pressable>
                    </View>

                    <View style={[loginStyles.footerTextContainer]}>
                        <NunitoSansText size={selectedFont.size} textColor="#000">Don't have an account? </NunitoSansText>
                        <Pressable onPress={GoToRegister}>
                            <NunitoSansText size={selectedFont.size} textColor={colorScheme.skyBlue}>Register Here</NunitoSansText>
                        </Pressable>
                    </View>
                </View>
                    
            </KeyboardAwareScrollView>
        </View>
    );

I hope this helps!

Best,
Abhay

@vipin14444
Copy link

After reading through countless threads and watching a lot of videos. I finally stumbled upon a thread from expo.
Keyboard Handling
Keyboard Handling tutorial for React Native apps

This uses react-native-reanimated and react-native-keyboard-controller.
PS: Please follow the instructions to install react-native-reanimated from official docs.

yarn add react-native-keyboard-controller

Wrap your app inside KeyboardProvider (_layout.tsx or App.tsx)

import { KeyboardProvider } from "react-native-keyboard-controller";
...
export default function Layout() {
  return (
    <KeyboardProvider statusBarTranslucent>
      ...
    </KeyboardProvider>
    )
}

PS: statusBarTranslucent prop can be removed according to needs.

Create a new hook file useGradualAnimation.ts

import { useKeyboardHandler } from "react-native-keyboard-controller";
import { useSharedValue } from "react-native-reanimated";

export const useGradualAnimation = () => {
  const height = useSharedValue(0);

  useKeyboardHandler(
    {
      onMove: (event) => {
        "worklet";
        height.value = Math.max(event.height, 0);
      },
    },
    []
  );
  return { height };
};

Create a new file KeyboardAwareView.tsx

import { StyleSheet, View, ViewProps } from "react-native";
import React from "react";
import { useGradualAnimation } from "@/hooks/useGradualAnimation";
import Animated, { useAnimatedStyle } from "react-native-reanimated";

export default function KeyboardAwareView({ children, style }: ViewProps) {
  const { height } = useGradualAnimation();
  const keyboardSizePaddingStyles = useAnimatedStyle(() => {
    return {
      height: Math.abs(height.value),
    };
  }, []);

  return (
    <View style={[styles.container, style]}>
      {children}
      <Animated.View style={keyboardSizePaddingStyles} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
});

Now, you can simply use this new component as a standalone replacement for the default KeyboardAwareView.
Please see the video attached above for a better explanation. Hope this helps. ✨

@umardev500
Copy link

the simple way is don't make KeyboardAvoidingView is root of element, instead you make View as root with flex 1

@ChromeQ
Copy link

ChromeQ commented Dec 22, 2024

No suggestions here have worked for me and I'm surprised so many are able to get a good outcome with a fixed magic number (100) - I'll tell you this is a fluke and by no means a solid or dynamic solution.
Since this issue is still open after 4 years all I can recommend was abandoning react-native KeyboardAvoidingView and using react-native-keyboard-controller as it solved this issue with the benefit of being consistent across iOS and android.

@AlanNin
Copy link

AlanNin commented Jan 6, 2025

hello, this is my solution

1. create a state for your keyboard:

  import { useState } from "react";
  const [isKeyboardVisible, setIsKeyboardVisible] = useState(false);

2. update the state on change:

import { useEffect } from "react";
import { Keyboard } from "react-native";

 React.useEffect(() => {
    const keyboardShowListener = Keyboard.addListener("keyboardDidShow", () => {
      setIsKeyboardVisible(true);
    });

    const keyboardHideListener = Keyboard.addListener("keyboardDidHide", () => {
      setIsKeyboardVisible(false);
    });

    return () => {
      keyboardShowListener.remove();
      keyboardHideListener.remove();
    };
  }, []);

3. render your component (i do recommend rendering the component KeyboardAvoidingView inside of a View component because if like me you are using custom colors for themes, you might notice some flickering when toggling the keyboard at the bottom of your screen if you use KeyboardAvoidingView as root):

note: my container has the background color of my theme, therefore the flickering doesn't happen, keyboardContainer has "flex: 1" because in my case I want it to take the rest of the screen. don't worry about "MarkdownTextInput" instead of "TextInput" it behaves the same, the parser prop it's from MarkdownTextInput library, you won't need that if you are not using it.

<View style={{ backgroundColor: "#2e2e2e" }}>
          <KeyboardAvoidingView
            style={{ flex: 1 }}
            behavior={Platform.OS === "ios" ? "padding" : "height"}
            keyboardVerticalOffset={Platform.OS === "ios" ? 0 : 64}
            enabled={isKeyboardVisible}
          >
            <MarkdownTextInput
              value={inputs.content}
              onChangeText={(e) => handleInputChange("content", e)}
              multiline={true}
              style={[styles.noteContent]}
              placeholder="Capture your thoughts..."
              parser={parseExpensiMark}
            />
          </KeyboardAvoidingView>
</View>

so, im using a View container to avoid a black (or white, depends on your phone theme) flickering and toggling enable to remove the empty space at the bottom of the screen.

hope this helps.

before:
Screenshot_1736193731

after:
Screenshot_1736193747

@abhaykumar181
Copy link

hello, this is my solution

1. create a state for your keyboard:

  import { useState } from "react";
  const [isKeyboardVisible, setIsKeyboardVisible] = useState(false);

2. update the state on change:

import { useEffect } from "react"; import { Keyboard } from "react-native";

 React.useEffect(() => {
    const keyboardShowListener = Keyboard.addListener("keyboardDidShow", () => {
      setIsKeyboardVisible(true);
    });

    const keyboardHideListener = Keyboard.addListener("keyboardDidHide", () => {
      setIsKeyboardVisible(false);
    });

    return () => {
      keyboardShowListener.remove();
      keyboardHideListener.remove();
    };
  }, []);

3. render your component (i do recommend rendering the component KeyboardAvoidingView inside of a View component because if like me you are using custom colors for themes, you might notice some flickering when toggling the keyboard at the bottom of your screen if you use KeyboardAvoidingView as root):

note: my container has the background color of my theme, therefore the flickering doesn't happen, keyboardContainer has "flex: 1" because in my case I want it to take the rest of the screen. don't worry about "MarkdownTextInput" instead of "TextInput" it behaves the same, the parser prop it's from MarkdownTextInput library, you won't need that if you are not using it.

<View style={{ backgroundColor: "#2e2e2e" }}>
          <KeyboardAvoidingView
            style={{ flex: 1 }}
            behavior={Platform.OS === "ios" ? "padding" : "height"}
            keyboardVerticalOffset={Platform.OS === "ios" ? 0 : 64}
            enabled={isKeyboardVisible}
          >
            <MarkdownTextInput
              value={inputs.content}
              onChangeText={(e) => handleInputChange("content", e)}
              multiline={true}
              style={[styles.noteContent]}
              placeholder="Capture your thoughts..."
              parser={parseExpensiMark}
            />
          </KeyboardAvoidingView>
</View>

so, im using a View container to avoid a black (or white, depends on your phone theme) flickering and toggling enable to remove the empty space at the bottom of the screen.

hope this helps.

before: Screenshot_1736193731

after: Screenshot_1736193747

Even though I haven't checked, I'm quite sure this works for a multiline text input. However, it may not work for a standard text input, as it could add a whitespace when the keyboard hides. Please let me know if it works or not.

@AllanWell
Copy link

the simple way is don't make KeyboardAvoidingView is root of element, instead you make View as root with flex 1

It's ridiculous,but it's WORKING

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API: Keyboard Component: KeyboardAvoidingView Needs: Attention Issues where the author has responded to feedback.
Projects
None yet
Development

No branches or pull requests