react-native-avoid-softinput

repository·main·Indexed 21 days ago

https://github.com/mateusz1913/react-native-avoid-softinput

A native solution for React Native that prevents focused views from being covered by the soft input (keyboard). It works by listening to soft input events and applying translations to the root view or bottom padding to scroll views on the native side. The library provides a set of React hooks (such as useSoftInputState and useSoftInputShown) and module methods to manage keyboard visibility, height changes, and Android windowSoftInputMode settings.

Tokens
16.1K
Snippets
58
Records
66
Agent score
67%

What's inside react-native-avoid-softinput

  1. Consider react-native-keyboard-controller as an alternative

    main

    If react-native-avoid-softinput does not meet your project requirements, react-native-keyboard-controller is a suggested alternative.

    Key features of react-native-keyboard-controller include:

    • Native Detection: Detects keyboard appearance on the native side and allows applying the detected height to Reanimated or vanilla Animated views.
    • Native Implementation: Written in Kotlin and Swift, utilizing the WindowInsetsCompat API on Android.
    • New Architecture Support: Supports Fabric & TurboModules (since version 1.2.0).
    • Interactive Keyboard: Supports interactive keyboard features (since version 1.5.0).
  2. Choose the right API for your keyboard use case

    main

    Avoid the anti-pattern of creating a single generic KeyboardAvoidingView that wraps everything. Instead, select an API based on your specific layout requirements:

    • Fullscreen scrollable forms: Use the AvoidSoftInput module to push the entire root view above the keyboard or scroll to the nearest covered text field.
    • Forms inside Modals/BottomSheets: Use the AvoidSoftInputView component, which pushes itself above the keyboard or handles scrolling for its internal elements.
    • Sticky footers (e.g., Submit buttons): If you need a footer to remain visible while the keyboard is up, manually apply padding to the footer. You can detect keyboard state and height using these hooks:
      • useSoftInputShown
      • useSoftInputHidden
      • useSoftInputHeightChanged
  3. Configure Android keyboard handling

    main

    To ensure react-native-avoid-softinput is solely responsible for keyboard management on Android (and to prevent issues like doubled padding), you must disable the default Android OS keyboard handling.

    Install react-native-edge-to-edge and follow its specific setup instructions for the best Android 15 support.

    Legacy Setup (without edge-to-edge)

    1. Modify Android Manifest

    In a bare RN or post-prebuild Expo project, set android:windowSoftInputMode to adjustResize in <projectDir>/android/app/src/main/AndroidManifest.xml:

    <activity
      android:name=".MainActivity"
      android:windowSoftInputMode="adjustResize"
      ...
    />

    In an Expo managed project, set softwareKeyboardLayoutMode to resize in your app.json or app.config.js.

    2. Enable iOS-like behavior

    Call AvoidSoftInput.setShouldMimicIOSBehavior(true) in your application code (e.g., in a root component or a specific screen) to ensure consistent behavior.

    <activity
      android:name=".MainActivity"
      android:windowSoftInputMode="adjustResize"
      android:launchMode="singleTask"
      android:exported="true">
  4. Use AvoidSoftInputView in Modals and Navigation

    main

    To ensure that the soft input (keyboard) does not overlap your UI components within a Modal or a specific navigation screen, wrap the content that needs to be adjusted with the AvoidSoftInputView component.

    When using navigation libraries like react-navigation, you may need to combine AvoidSoftInputView with the library's specific hooks (such as useFocusEffect) to trigger actions when a screen gains or loses focus, ensuring the soft input avoidance logic stays in sync with the navigation state.

    import * as React from "react";
    import { Button, Modal, ScrollView, TextInput, View } from "react-native";
    import { AvoidSoftInputView } from "react-native-avoid-softinput";
    import { useFocusEffect } from "@react-navigation/native";
    
    export const ModalExample: React.FC = () => {
      const [ modalVisible, setModalVisible ] = React.useState(false);
    
      function closeModal() {
        setModalVisible(false);
      }
    
      function openModal() {
        setModalVisible(true);
      }
    
      return <View>
        <Button
          onPress={openModal}
          title="Open modal"
        />
        <Modal
          navigationBarTranslucent={true}
          onRequestClose={closeModal}
          statusBarTranslucent={true}
          visible={modalVisible}
        >
          <View>
            <View>
              <View>
                <Button onPress={closeModal} title="X" />
              </View>
              <AvoidSoftInputView>
                <ScrollView>
                  <View>
                    <TextInput placeholder="Single line" />
                    <TextInput placeholder="Multiline" />
                    <Button
                      onPress={closeModal}
                      title="Submit"
                    />
                  </View>
                </ScrollView>
              </AvoidSoftInputView>
            </View>
          </View>
        </Modal>
      </View>;
    };
  5. Implement a fullscreen form with react-native-avoid-softinput

    main

    When building fullscreen forms where all elements are inside a ScrollView, the goal is to ensure that focused inputs are pushed above the keyboard and that the submit button remains accessible when scrolling to the bottom.

    To implement this pattern:

    1. Use AvoidSoftInput.setEnabled(true) when the form screen gains focus to enable keyboard avoidance logic.
    2. Use AvoidSoftInput.setEnabled(false) when the screen loses focus to disable it.
    3. Wrap your form content in a ScrollView.
    4. (Optional) Use the useSoftInputAppliedOffsetChanged hook to monitor the appliedOffset value if you need to perform custom logic based on how much the view is being offset to avoid the keyboard.
    import { useFocusEffect } from '@react-navigation/native';
    import * as React from 'react';
    import { ScrollView, View } from 'react-native';
    import { AvoidSoftInput, useSoftInputAppliedOffsetChanged } from 'react-native-avoid-softinput';
    
    export const FormExample: React.FC = () => {
      // Enable avoidance when the screen is focused, disable when it is not
      const onFocusEffect = React.useCallback(() => {
        AvoidSoftInput.setEnabled(true);
        return () => {
          AvoidSoftInput.setEnabled(false);
        };
      }, []);
    
      useFocusEffect(onFocusEffect);
    
      // Monitor the offset applied to avoid the keyboard
      useSoftInputAppliedOffsetChanged(({ appliedOffset }) => {
        console.log({ appliedOffset });
      });
    
      return (
        <ScrollView>
          <View>
            {/* Your form inputs here */}
          </View>
          <View>
            {/* Your submit button here */}
          </View>
        </ScrollView>
      );
    };
  6. Migrate from v6 to v7 and handle Android 15 edge-to-edge mode

    main

    Starting from v7, react-native-avoid-softinput supports detection of react-native-edge-to-edge. To ensure the best support for Android 15's "edge to edge" mode, it is recommended to migrate to react-native-edge-to-edge.

    Additionally, the function AvoidSoftInput.setShouldMimicIOSBehavior is deprecated as of v7 and is scheduled for removal in future major versions. If you were using this function to manually handle keyboard insets on Android to match iOS behavior, you should transition to the recommended setup described in the installation guide.

    useEffect(() => {
    -  AvoidSoftInput.setShouldMimicIOSBehavior(true); // <---- Tell Android that library will handle keyboard insets manually to match iOS behavior
      AvoidSoftInput.setEnabled(true); // <---- Enable module
    }, []);
  7. Handle forms inside React Native Modals

    main

    When placing forms inside a React Native Modal component, you need to ensure that inputs are pushed above the keyboard and that action buttons (like 'Submit') remain accessible when scrolling.

    To achieve this, wrap the content inside the Modal (typically a ScrollView or similar scrollable container) with the AvoidSoftInputView component. This ensures that the keyboard visibility triggers the necessary layout adjustments within the modal's context.

    import { Modal, ScrollView, View } from 'react-native';
    import { AvoidSoftInputView } from 'react-native-avoid-softinput';
    
    // ... inside your component
    <Modal visible={modalVisible} transparent={true}>
      <View style={styles.modalContent}>
        <AvoidSoftInputView style={{ flex: 1 }}>
          <ScrollView>
            <View>
              {/* Your form inputs and buttons go here */}
              <TextInput placeholder="Single line" />
              <TextInput placeholder="Multiline" multiline={true} />
              <Button title="Submit" onPress={handleSubmit} />
            </View>
          </ScrollView>
        </AvoidSoftInputView>
      </View>
    </Modal>
  8. Mock react-native-avoid-softinput using the `__mocks__` directory

    main

    To use the library's built-in Jest mock via the manual mock directory pattern, create a file at __mocks__/react-native-avoid-softinput.js and export the mock provided by the library. This approach allows you to easily override specific parts of the mock (like useSoftInputState) using Object.assign if your tests require specific state values.

    const mock = require('react-native-avoid-softinput/jest/mock');
    
    /**
     * If needed, override mock like so:
     *
     * module.exports = Object.assign(mock, { useSoftInputState: jest.fn(() => ({ isSoftInputShown: true, softInputHeight: 300 })) });
     */
    
    module.exports = mock;
  9. Configure Kotlin version for Android

    main

    Since the library is implemented in Kotlin, you may need to explicitly specify the kotlinVersion in your Android project to avoid build failures. The version should be compatible with your React Native or Expo SDK version.

    For Bare React Native or Expo (after prebuild)

    Modify <projectDir>/android/build.gradle:

    buildscript {
        ext {
            kotlinVersion = "1.8.0" // Use a version compatible with your project
        }
    }

    For Expo Managed Projects (before prebuild)

    Install expo-build-properties and configure it in your app.json or app.config.js:

    npx expo install expo-build-properties
    {
      "expo": {
        "plugins": [
          [
            "expo-build-properties",
            {
              "android": {
                "kotlinVersion": "1.8.0"
              }
            }
          ]
        ]
      }
    }
  10. Animate using AvoidSoftInput module events and hooks

    main

    You can trigger animations by listening to soft input lifecycle events. The library provides two ways to access these events:

    1. Module Methods

    Use the AvoidSoftInput module directly to listen for events:

    • AvoidSoftInput.onSoftInputShown
    • AvoidSoftInput.onSoftInputHidden
    • AvoidSoftInput.onSoftInputHeightChange
    • AvoidSoftInput.onSoftInputAppliedOffsetChange

    2. Shortcut Hooks

    If you are using useEffect or want a more idiomatic React approach, use these hooks:

    • useSoftInputShown
    • useSoftInputHidden
    • useSoftInputHeightChanged
    • useSoftInputAppliedOffsetChanged

    These can be used with React Native's Animated API or the react-native-reanimated library to smoothly transition UI elements (like margins or heights) when the keyboard appears or disappears.

    const animatedValue = useAnimatedValue(0);
    
    useSoftInputShown(({ softInputHeight }) => {
      Animated.timing(animatedValue, {
        toValue: softInputHeight,
        duration: 1000,
      }).start();
    });
    
    useSoftInputHidden(() => {
      Animated.timing(animatedValue, {
        toValue: 0,
        duration: 1000,
      }).start();
    });
    
    // ... apply animatedValue to an <Animated.View />
  11. Implement a sticky footer with soft input height changes

    main

    When building complex layouts where some UI elements (like text fields) are inside a ScrollView and others (like a CTA button) are fixed at the bottom, the library's automatic avoidance might not cover the fixed elements.

    To handle a "sticky" footer manually, you can use the useSoftInputHeightChanged hook to detect changes in the keyboard/soft input height and apply that value as padding to your button's container. This ensures the button is pushed up above the keyboard when it appears.

    Alternatively, you can achieve this by combining useSoftInputShown and useSoftInputHidden hooks to toggle the padding value.

    import { useSoftInputHeightChanged } from 'react-native-avoid-softinput';
    import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
    
    // ... inside component
    const buttonContainerPaddingValue = useSharedValue(0);
    
    const buttonContainerAnimatedStyle = useAnimatedStyle(() => {
      return {
        paddingBottom: buttonContainerPaddingValue.value,
      };
    });
    
    // Use this hook to react to keyboard height changes
    useSoftInputHeightChanged(({ softInputHeight }) => {
      buttonContainerPaddingValue.value = withTiming(softInputHeight);
    });
    
    // ... in JSX
    <Animated.View style={[buttonContainerAnimatedStyle, styles.ctaButtonWrapper]}>
      <Button onPress={NOOP} title="Submit" />
    </Animated.View>
  12. Handle soft input avoidance in Bottom Sheets

    main

    When using a bottom sheet, the method for avoiding the soft input depends on how the bottom sheet is implemented:

    1. If using React Native Modal: Use the AvoidSoftInputView component.
    2. For other implementations (e.g., @gorhom/react-native-bottom-sheet): Use the AvoidSoftInput module to manually enable avoidance and set offsets when the screen or component gains focus.

    To ensure the soft input avoidance is only active when the bottom sheet is visible, use a lifecycle hook (like useFocusEffect from React Navigation) to enable the module and set an appropriate offset on mount, and disable it/reset the offset on unmount.

    import { AvoidSoftInput } from 'react-native-avoid-softinput';
    import { useFocusEffect } from '@react-navigation/native';
    
    // ... inside your component
    const onFocusEffect = React.useCallback(() => {
      // Enable avoidance and set the offset required for the bottom sheet
      AvoidSoftInput.setEnabled(true);
      AvoidSoftInput.setAvoidOffset(70);
    
      return () => {
        // Reset offset and disable avoidance when leaving the screen
        AvoidSoftInput.setAvoidOffset(0);
        AvoidSoftInput.setEnabled(false);
      };
    }, []);
    
    useFocusEffect(onFocusEffect);