How to handle keyboard avoiding view in React-Native

Will we ever be safe from KeyboardAvoidingView?

Please find the video where William Candillion is talking about KeyboardAvoidingView. He noted at the end of the talk that if any company is hiring React-Native developer in an interview process If anyone can solve the problem of KeyboardAvoidingView, then you get hired immediately.

The KeyboardAvoidingView usually persist in android only.

How I tackled the issue?

In one of our React-Native project, we have TextInput at the bottom of the view. You can imagine it like WhatsApp typing box. When the TextInput is focused, then the Bottom component along with the should move up. To achieve that I created a WrapperComponent where I added KeyboardAvoidingView. You can check the code snippet below.

Android Configuration

Before moving ahead, we need to do a small configuration in AndroidManifest.xml file.

Find android:windowSoftInputMode and add "adjustPan|adjustResize" to it.

android:windowSoftInputMode="adjustPan|adjustResize"

Once you complete the above configuration, then you can proceed ahead.

I am using TypeScript

Create a file keyboard-avoiding-wrapper-view.tsx and add the following code.

import React from "react";
import {
  KeyboardAvoidingView,
  Platform,
  NativeModules,
  StyleProp,
  ViewStyle,
} from "react-native";

const { StatusBarManager } = NativeModules;

interface KeyboardAvoidingWrapperProps {
  style: StyleProp<ViewStyle>;
}

interface StatusBarManagerType {
  height: number;
}

const KeyboardAvoidingWrapper: React.FunctionComponent<KeyboardAvoidingWrapperProps> = (
  props
) => {
  const [statusBarHeight, setStatusBarHeight] = React.useState < number > 0;

  React.useEffect(() => {
    if (Platform.OS === "ios") {
      StatusBarManager.getHeight(({ height }: StatusBarManagerType) => {
        setStatusBarHeight(height);
      });
    }
  }, []);

  return (
    <KeyboardAvoidingView
      style={props.style}
      behavior={Platform.OS === "ios" ? "padding" : undefined}
      keyboardVerticalOffset={
        Platform.OS === "ios" ? statusBarHeight : undefined
      }
    >
      {props.children}
    </KeyboardAvoidingView>
  );
};

export default KeyboardAvoidingWrapper;

As you can see from the snippet above, we are setting the value for keyboardVerticalOffset to be equal to the statusBarHeight. We are getting the statusBarHeight using the below useEffect and setting the statusBarHeight. We are using useState to manage the state.

//
//
const [statusBarHeight, setStatusBarHeight] = React.useState < number > 0;

React.useEffect(() => {
  if (Platform.OS === "ios") {
    StatusBarManager.getHeight(({ height }: StatusBarManagerType) => {
      setStatusBarHeight(height);
    });
  }
}, []);

//

//

Using the wrapper component

Now we have created a wrapper component now let us try to use it practically.

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

const App = () => {

 return (<View style={{flex: 1}}>
 <View style={{flex: 8}>


 </View>
 <View style={{flex: 2}>
 {Array.from({length: 20}, () => Math.floor(Math.random() * 20)).map((number, index) => <Text key={index}>{number}</Text>)}
 </View>
 <TextInput text="sample text" />
 </View>)
}

You may think we are done now. But there's a catch here. When the keyboard opens the TextInput component is behind the key-board view.

The problem is only for iOS device. The problem persists only when the keyboard is open.

So we need to find a way to tackle this issue. We can listen if the keyboard is open or not. To do that, follow the code snipped below.

This will listen to the keyboard event.

const [isKeyboardOpen, setIsKeyboardOpen] = useState(false);
const keyboardShowListener = useRef(null);
const keyboardHideListener = useRef(null);

useEffect(() => {
  keyboardShowListener.current = Keyboard.addListener("keyboardDidShow", () =>
    setIsKeyboardOpen(true)
  );
  keyboardHideListener.current = Keyboard.addListener("keyboardDidHide", () =>
    setIsKeyboardOpen(false)
  );

  return () => {
    keyboardShowListener.current.remove();
    keyboardHideListener.current.remove();
  };
});

Now we can update our KeyboardAvoidingWrapper component.

import React from "react";
import {
  KeyboardAvoidingView,
  Platform,
  NativeModules,
  StyleProp,
  ViewStyle,
} from "react-native";

const { StatusBarManager } = NativeModules;

interface KeyboardAvoidingWrapperProps {
  style: StyleProp<ViewStyle>;
}

interface StatusBarManagerType {
  height: number;
}

const KeyboardAvoidingWrapper: React.FunctionComponent<KeyboardAvoidingWrapperProps> = (
  props
) => {
  const [statusBarHeight, setStatusBarHeight] = React.useState < number > 0;
  const [isKeyboardOpen, setIsKeyboardOpen] = useState(false);
  const keyboardShowListener = useRef(null);
  const keyboardHideListener = useRef(null);

  useEffect(() => {
    keyboardShowListener.current = Keyboard.addListener("keyboardDidShow", () =>
      setIsKeyboardOpen(true)
    );
    keyboardHideListener.current = Keyboard.addListener("keyboardDidHide", () =>
      setIsKeyboardOpen(false)
    );

    return () => {
      keyboardShowListener.current.remove();
      keyboardHideListener.current.remove();
    };
  });

  // For some reason 44 was working in all the simulators.
  // I have no justification to it why it worked.
  const iOSDefaultPadding = isOpen ? 0 : 44;

  React.useEffect(() => {
    if (Platform.OS === "ios") {
      StatusBarManager.getHeight(({ height }: StatusBarManagerType) => {
        setStatusBarHeight(height);
      });
    }
  }, []);

  return (
    <KeyboardAvoidingView
      style={props.style}
      behavior={Platform.OS === "ios" ? "padding" : undefined}
      keyboardVerticalOffset={
        Platform.OS === "ios" ? statusBarHeight + iOSDefaultPadding : undefined
      }
    >
      {props.children}
    </KeyboardAvoidingView>
  );
};

export default KeyboardAvoidingWrapper;

We this KeyboardAvoidingWrapper is completed. I have also created a custom hook which will tell us if the keyboard is open or close. Please refer to the hooks below.

useKeyboardStatus Hook

I have created a custom hook which can be used to listen to know if the React Native keyboard is open or close. This hook is listening to an event such as keyboardWillHide and keyboardWillShow.

import React, { useState, useRef } from "react";
import { Keyboard } from "react-native";

/**
 * Returns if the keyboard is open/closed
 *
 * @return {bool} isKeyboardOpen
 */
export function useKeyboardStatus() {
  const [isKeyboardOpen, setIsKeyboardOpen] = useState(false);
  const keyboardShowListener = useRef(null);
  const keyboardHideListener = useRef(null);

  useEffect(() => {
    keyboardShowListener.current = Keyboard.addListener("keyboardDidShow", () =>
      setIsKeyboardOpen(true)
    );
    keyboardHideListener.current = Keyboard.addListener("keyboardDidHide", () =>
      setIsKeyboardOpen(false)
    );

    return () => {
      keyboardShowListener.current.remove();
      keyboardHideListener.current.remove();
    };
  });

  return isKeyboardOpen;
}

The above hook will work only in the functional component.

There is a popular question in Stack overflow regarding the keyboard issue in Android. How to avoid keyboard pushing layout up on Android react-native. Follow the link to find out how I solved the issue.

To sum it up

I hope this post will help you understand the problem and find you a way to fix the issue.

💌 If you'd like to receive more tutorials in your inbox, you can sign up for the newsletter here.

Discussions

Up next