68

I am using React-Navigation in my app and the app consists of StackNavigator with multiple screens, some screens of which have TextInput with autoFocus={true}

Problem: on these screens when the component renders, the height of the screen is being set in the constructor:

constructor(props) {
    super(props);
    this.state = { 
        height: Dimensions.get('window').height,
    };
}

But, since the autoFocus of TextInput is true, the keyboard on this screen pops-up on the screen almost instantly after the render, causing the component to re-render due to the eventListener that is added to Keyboard in componentWillMount:

 componentWillMount() {
    this.keyboardWillShowListener = Keyboard.addListener(
        "keyboardWillShow",
        this.keyboardWillShow.bind(this)
    );
}

keyboardWillShow(e) {
    this.setState({
        height:
            Dimensions.get("window").height * 0.9 - e.endCoordinates.height
    });
    LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
}

This affects the performance and I would like to avoid the unnecessary re-renders.

Questions:
1. Is it possible to set the dynamic height (depending on the device) of the Keyboard in React-Navigation's ScreenProps?
2. Is it possible to do the same with React-Navigation's state.params?
3. Is there any other way to solve this problem, apart from applying KeyboardAvoidingView or this module ?

Eduard
  • 8,437
  • 10
  • 42
  • 64

5 Answers5

94

This is what I did:

If the app has "Authorization / Log-in / Sign-up screen" then:

  1. In componentWillMount add KeyboardListeners as explained here:

    this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow);
    this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide);
    
  2. Add autoFocus to e-mail / phone number / any other "first" TextInput on the page, so that when the screen loads, the Keyboard pops-up.

  3. In _keyboardDidShow function, that is used as a KeyboardListener, do the follows:

    _keyboardDidShow(e) {
        this.props.navigation.setParams({
            keyboardHeight: e.endCoordinates.height,
            normalHeight: Dimensions.get('window').height, 
            shortHeight: Dimensions.get('window').height - e.endCoordinates.height, 
        }); 
    }
    

    Dimensions is an API of React-Native, do not forget to import it just like you import any React-Native component.

  4. After that, while redirecting to the next page, pass these params and do not forget to keep on passing them to other screens in order not to lose this data:

    this.props.navigation.navigate('pageName', { params: this.props.navigation.state.params });
    
Eduard
  • 8,437
  • 10
  • 42
  • 64
91

I also needed a hook for it, so that is how I get the height of the keyboard (largely inspired by the other answer), the code example is in TypeScript:

import { useEffect, useState } from 'react';
import { Keyboard, KeyboardEvent } from 'react-native';

export const useKeyboard = () => {
  const [keyboardHeight, setKeyboardHeight] = useState(0);

  useEffect(() => {
    function onKeyboardDidShow(e: KeyboardEvent) { // Remove type here if not using TypeScript
      setKeyboardHeight(e.endCoordinates.height);
    }

    function onKeyboardDidHide() {
      setKeyboardHeight(0);
    }

    const showSubscription = Keyboard.addListener('keyboardDidShow', onKeyboardDidShow);
    const hideSubscription = Keyboard.addListener('keyboardDidHide', onKeyboardDidHide);
    return () => {
      showSubscription.remove();
      hideSubscription.remove();
    };
  }, []);

  return keyboardHeight;
};

then in your component:

  const keyboardHeight = useKeyboard();
  console.log(keyboardHeight);
Constantin
  • 3,655
  • 1
  • 14
  • 23
Kevin Amiranoff
  • 13,440
  • 11
  • 59
  • 90
  • This worked great for me. I needed an alternative for a Flatlist embedded in a ScrollView using KeyboardAvoidingView as there is a conflict with wrapping a Flatlist in a ScrollView. In my case it is unavoidable because the Flatlist is spontaneously generated so none of the other options worked. Thank you! – GenericJam May 30 '20 at 13:14
  • You should move `OnKeyboardDidShow` and `onKeyboardDidHide` functions to the inside of the effect because otherwise there will/may be dependency warnings. – Nordling Art Apr 02 '21 at 16:40
  • 1
    No reason to return an array containing the keyboard height, just return `keyboardHeight` instead. Nice hook though. – ICW Aug 15 '21 at 16:42
  • i found useKeyboard make my screen re-render 3 times when keyboard show. really bad for ui – famfamfam Jul 25 '22 at 07:16
22

For those of you still looking for an answer to this now you can use hooks.

import { useKeyboard } from '@react-native-community/hooks'

//Then keyboard like this 

const keyboard = useKeyboard()

console.log('keyboard isKeyboardShow: ', keyboard.keyboardShown)
console.log('keyboard keyboardHeight: ', keyboard.keyboardHeight)
Lazerbeak12345
  • 309
  • 4
  • 19
Shawn
  • 351
  • 1
  • 3
  • 9
  • I tried this, but I couldn't find a good place to call `useKeyboard` that didn't complain. Could you elaborate upon where that is supposed to work? (no, top-level didn't work for me) – Lazerbeak12345 Aug 19 '20 at 15:38
  • @Lazerbeak12345, it should be used in functional components. – Sang Dang Sep 25 '20 at 07:20
  • 1
    you only get the height of this keyboard after it has been shown. once the keyboard is shown it's already too late for my purpose using it for the KeyboardAvoidingView-component. KeyboardAvoidingView doesn't update spacings after it's shown. – J-ho Apr 05 '22 at 09:12
10

Just would like to add to the above answers, that using keyboardWillShow and keyboardWillHide rather than keyboardDidShow and keyboardDidHide will look much better. It just runs sooner and hence, looks smoother.

Ethan Naluz
  • 311
  • 5
  • 4
  • Why does everyone use `Did` below? Maybe there's a reason they didn't use `Will`? – Noitidart May 22 '20 at 20:20
  • 2
    One potential reason I can think of is that the callback passed to `keyboardDidShow`/`keyboardDidHide` could be analogous to `componentDidMount` (you put operations here that you don't want to block the UI), and in the case that the `Will` variants of these events are blocking to the actual showing and hiding of the keyboard (especially if doing expensive operations in the callbacks), using `Did` would be a better option. In my case, using `Will` was better because I wanted specific things to happen in the UI that I wanted to happen as quickly as possible. – Ethan Naluz May 23 '20 at 22:06
  • 1
    I used `Did` because I assumed on `Will` I wouldn't get the height of the keyboard. I might have assumed wrong though. – Kevin Amiranoff May 24 '20 at 13:28
  • 9
    In my testing `Will` is not firing on Android. So it seems they had Android in mind. @KevinAmiranoff on iOS using `Will` also comes with keyboard height. – Noitidart May 25 '20 at 00:12
  • If you have to show some pop-up, either `Alert` or `ActionSheet` on hiding keyboard, you have to put it in `keyboardDidHide` since executing in `keyboardWillHide` causes side effects like triggering hide keyboard multiple times and showing keyboard again after pop-up is closed. – Dangular Oct 06 '21 at 04:51
  • 1
    Be careful because these props only work for iOS and not android – Uch Jun 16 '23 at 06:31
0

You can create your own component and call it like this no third party package required.

import React, { useEffect, useLayoutEffect, useState } from "react";
import {
    Keyboard,
    KeyboardEvent,
    View
} from "react-native";
import { TComponent } from "./constraints";
import styles from "./styles";

export const KeyboardSpacerView: React.FC<TComponent> = (props) => {

    const [height, setHeight] = useState<number>(0);

    useEffect(()=>{
        // let event1 = Keyboard.addListener('keyboardWillShow', keyboardWillShow);
        let event2 = Keyboard.addListener('keyboardDidShow', keyboardWillShow);
        let event3 = Keyboard.addListener('keyboardWillHide', keyboardWillHide);
        
        return ()=>{
            // Keyboard.removeSubscription(event1);
            Keyboard.removeSubscription(event2);
            Keyboard.removeSubscription(event3);
        }
    }, [false])

    const keyboardWillShow = (event: KeyboardEvent) => {
        setHeight(event.endCoordinates.height);
    }

    const keyboardWillHide = (event: KeyboardEvent) => {
        setHeight(0);
    }

    return (
        <View style={[styles.container, {
            height: height
        }]} />
    );
}

and Use it like this

<View style={{flex: 1}}>
   <FlatList
      data={data}
      renderItem={Row}
   />
   <KeyboardSpacerView />
</View>