21

I'm using React Natives KeyboardAvoidingView to set the height of my View when the Keyboard is shown. But when I close the the keyboard in the app, the height of the View is not changed back to it's original value.

<KeyboardAvoidingView behavior="height" style={styles.step}>
  <View style={styles.stepHeader}>
    // my content
  </View>
</KeyboardAvoidingView>

The View with the red outline did take up the whole space before I opened and closed the keyboard.

Screenshot

mxmtsk
  • 4,285
  • 6
  • 22
  • 47
  • I experience the same issue. [iOS] Have you found a solution? – Edgar Mar 21 '17 at 18:39
  • 1
    @Edgar For some cases I switched to the following package which works, but the Component that RN offers still doesn't work. https://github.com/APSL/react-native-keyboard-aware-scroll-view – mxmtsk Mar 21 '17 at 23:08

5 Answers5

26

A more detailed explanation on Nisarg's answer.

Create a key for the KeyboardAvoidingView in the constructor

constructor(props) {
    this.state = {
      keyboardAvoidingViewKey: 'keyboardAvoidingViewKey',
    }
}

add listener on the keyboard's will/did hide (and remove it in the willUnmount)

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

componentDidMount() {
    // using keyboardWillHide is better but it does not work for android
    this.keyboardHideListener = Keyboard.addListener(Platform.OS === 'android' ? 'keyboardDidHide': 'keyboardWillHide', this.keyboardHideListener.bind(this));
}

componentWillUnmount() {
    this.keyboardHideListener.remove()
}

update the keyboardAvoidingViewKey in the keyboardHideListener function, should be a new value each time (I used a timestamp) and use this key when rendering the KeyboardAvoidingView element.

keyboardHideListener() {
    this.setState({
        keyboardAvoidingViewKey:'keyboardAvoidingViewKey' + new Date().getTime()
    });
}

render() {
    let { keyboardAvoidingViewKey } = this.state
    return (
        <KeyboardAvoidingView behavior={'height'} key={keyboardAvoidingViewKey} style={...}>
            ...
        </KeyboardAvoidingView>
    )
}

Note: Keep in mind that this will recreate the elements inside the KeyboardAvoidingView (i.e: will call their constructor function, I'm not quite sure why, I'll update the answer after deeper investigation), so you'll have to keep track of any state/prop values that might be overwritten

Update

After a much deeper investigation, I now know why the views are recreated once you change the key. In order to truly understand why it happens, one must be familiar with how react-native dispatches the render commands to the native side, this particular explanation is pretty long, if it interests you, you can read my answer here. In short, react-native uses Reactjs to diff the changes that should be rendered, these diffs are then sent as commands to a component named UIManager, which sends imperative commands that translate into a layout tree, which changes the layout based on the diff commands. Once you set a key on a component, reactjs uses this key to identify changes to said component, if this key changes, reactjs identifies the component as a completely new one, which in return sends the initial command to create said component, making all it's children to be created from scratch because there are identified as new elements in a new layout tree, deleting the old tree and creating a new one instead of just adjusting the diffs

If you would like, you can actually spy on these dispatched messages by adding the following code to your App.js file:

import MessageQueue from 'react-native/Libraries/BatchedBridge/MessageQueue'
const spyFunction = (msg) => {
    console.log(msg);
};

MessageQueue.spy(spyFunction);

If you do that, you'll notice in the logs that each time the key changes, the command that is dispatched in return is createViews, which like stated above creates all the elements that are nested under said component.

Samer Murad
  • 2,479
  • 25
  • 27
  • Thanks for the detailed explanation. If you find out why it does recreate the elements I'd appreciate and update on your answer! – mxmtsk Sep 16 '17 at 20:10
  • In my case, the keyboard just does not go away, keep popping up and down. – Eduard Oct 15 '17 at 16:48
  • this is probably another kind of issue, what you are experiencing, this code doesn't affect the keyboard, only the layout that is hidden behind of the keyboard, read the question, it says > when I close the the keyboard in the app, the height of the View is not changed back to it's original value. – Samer Murad Oct 16 '17 at 09:57
  • Worked like a charm. Thanks! – dsternlicht Feb 21 '18 at 13:57
  • 2
    If you get `this.setState` is not a function, change `this.keyboardHideListener` to `this.keyboardHideListener.bind(this)` in `componentDidMount`. – jinglesthula Jul 10 '18 at 17:02
  • @SamerMurad You bet :) – jinglesthula Oct 09 '18 at 14:46
  • Thanks for this answer! Saved me some pain. FYI I needed to change `key={keyboardAvoidingViewKey}` to `key={this.state.keyboardAvoidingViewKey}` on the KeyboardAvoidingView. – Banjer Nov 08 '18 at 01:50
8

Please give key to KeyboardAvoidingView and change when keyboard open and close so it will render and take height

<KeyboardAvoidingView behavior="height" style={styles.step} key={values}>
Nisarg Thakkar
  • 1,497
  • 16
  • 25
2

Wrap components in <KeyboardAvoidingView behavior="padding" style={styles}> on iOS and <View style={styles}> on android

render() {
    const ScrollContainer: View | KeyboardAvoidingView = 
    this.renderDependingOnPlatform();
    const scrollContainerParams: any = {};
    if (isIOS)
        scrollContainerParams.behavior = "padding";
return (
<ScrollContainer style={styles.container} {...scrollContainerParams}>
Scroll and other components
</ScrollContainer>
)}

/**
 * Render scroll container depending on platform
 * @returns {any}
 */
renderDependingOnPlatform() {
    if (isAndroid())
        return View;
    else return KeyboardAvoidingView;
}
0

A simple workaround is to set the behavior property of the KeyboardAvoidingView to 'padding'. This avoids the issue of recalling the constructor function, which allows you to safely store information in state (say you have two Inputs and you want to store the value of the text in state even if the user collapses the keyboard in between clicking the two inputs).

This method may slightly alter the layout of the KeyboardAvoidingView's children, so be aware of that.

Rainbow Fossil
  • 330
  • 2
  • 6
  • This fixed it for me. The layout was altered but the height offset came from my hidden tabbar, so it was easy to fix. – Okikioluwa May 27 '21 at 17:13
0

This is the same code as Samur just for my react-native functional component.

const [keyboardAvoidingViewKey, setKeyboardAvoidingViewKey] = useState(
    'keyboardAvoidingViewKey',
  );

  const keyboardHideListenerCallback = useCallback(() => {
    setKeyboardAvoidingViewKey(
      'keyboardAvoidingViewKey' + new Date().getTime(),
    );
  }, []);

  useEffect(() => {
    const keyboardHideListener = Keyboard.addListener(
      Platform.OS === 'android' ? 'keyboardDidHide' : 'keyboardWillHide',
      keyboardHideListenerCallback,
    );

    return () => {
      keyboardHideListener.remove();
    };
  }, [keyboardHideListenerCallback]);
MomasVII
  • 4,641
  • 5
  • 35
  • 52