react-native-keyboard-controller

repository·main·Indexed 23 days ago

https://github.com/kirillzyusko/react-native-keyboard-controller

A universal keyboard handling solution for React Native providing consistent behavior and smooth animations on iOS and Android. It includes prebuilt components like KeyboardStickyView, KeyboardAwareScrollView, and a drop-in replacement for KeyboardAvoidingView, as well as specialized tools for chat interfaces and interactive keyboard gestures. The library supports the React Native New Architecture (Fabric) and provides hooks like useKeyboardHandler and useReanimatedFocusedInput for complex animations and layout detection.

Tokens
47.2K
Snippets
101
Records
234
Agent score
81%

What's inside react-native-keyboard-controller

  1. Overview of react-native-keyboard-controller

    main
    react-native-keyboard-controller is a universal keyboard handling solution for React Native designed for smooth animations and consistent behavior across iOS and Android. It provides tools to map keyboard movement to animated values, support Reanimated, and handle interactive keyboard dismissing.
  2. Explore react-native-keyboard-controller features and components

    main

    The library provides a wide range of prebuilt components and utilities for keyboard management:

    • Prebuilt Components: KeyboardStickyView, KeyboardAwareScrollView, and a reworked KeyboardAvoidingView.
    • UI Enhancements: KeyboardToolbar (with customizable previous, next, and done buttons), OverKeyboardView (to display content over the keyboard without dismissing it), KeyboardBackgroundView (to match keyboard background), and KeyboardEffects.
    • Specialized Views: KeyboardChatScrollView for building chat interfaces and KeyboardExtender for adding custom buttons/UI to the keyboard.
    • Advanced Capabilities: Mapping keyboard movement to animated values, keyboardWillShow/keyboardWillHide events on Android, preloading the keyboard to avoid focus lag, and Reanimated support.
  3. Use KeyboardGestureArea to control keyboard position via gestures

    main

    KeyboardGestureArea allows you to define a specific region on the screen where user gestures will control the keyboard's position (e.g., for interactive dismissing or showing).

    Platform Availability:

    • Android: Available on Android >= 11. For Android < 11, it renders as a React.Fragment (no effect).
    • iOS: Fully supported.
    <KeyboardGestureArea
      interpolator="ios"
      offset={50}
      textInputNativeID="composer"
    >
      <ScrollView keyboardDismissMode="interactive">
        {/* The other UI components of application in your tree */}
      </ScrollView>
      <TextInput nativeID="composer" />
    </KeyboardGestureArea>
  4. Understand the layout-free keyboard animation approach

    main

    Version 1.21 introduces a new mental model for keyboard handling to avoid 'layout thrashing'. Instead of animating paddingBottom or height (which triggers expensive full layout passes every frame), the library uses a scroll-based approach.

    By extending the scrollable area rather than shrinking the container, the library avoids re-measuring the view tree. This is achieved by mimicking iOS contentInset behavior on Android, allowing for smooth 120 FPS animations without visual glitches or performance hits caused by the React Native layout engine.

  5. Build a custom keyboard animation hook for React Navigation

    main

    The default useKeyboardAnimation hook changes the Android softInputMode on component mount/unmount. In deep navigation stacks (like react-navigation), this can cause all subsequent screens to inherit adjustResize because previous screens remain mounted.

    To ensure softInputMode is only set to adjustResize while a specific screen is focused and restored to default when the screen is blurred, you can implement a custom hook using useFocusEffect from @react-navigation/native and the primitives from react-native-keyboard-controller.

    import { useContext, useCallback } from "react";
    import { useFocusEffect } from "@react-navigation/native";
    import {
      KeyboardController,
      AndroidSoftInputModes,
      useKeyboardContext,
    } from "react-native-keyboard-controller";
    
    function useKeyboardAnimation() {
      useFocusEffect(
        useCallback(() => {
          KeyboardController.setInputMode(
            AndroidSoftInputModes.SOFT_INPUT_ADJUST_RESIZE,
          );
    
          return () => KeyboardController.setDefaultMode();
        }, []),
      );
    
      const context = useKeyboardContext();
    
      return context.animated;
    }
  6. Integrate KeyboardChatScrollView with virtualized lists (FlatList, FlashList, LegendList)

    main

    Since KeyboardChatScrollView is a custom scroll component, you can integrate it with third-party virtualized lists using their renderScrollComponent prop.

    To ensure correct behavior, create a wrapper component that forwards the ref and sets automaticallyAdjustContentInsets={false} and contentInsetAdjustmentBehavior="never".

    import React, { forwardRef } from "react";
    import {
      KeyboardChatScrollView,
      type KeyboardChatScrollViewRef,
    } from "react-native-keyboard-controller";
    import type { ScrollViewProps } from "react-native";
    import type { KeyboardChatScrollViewProps } from "react-native-keyboard-controller";
    
    const VirtualizedListScrollView = forwardRef<
      KeyboardChatScrollViewRef,
      ScrollViewProps & KeyboardChatScrollViewProps
    >((props, ref) => {
      return (
        <KeyboardChatScrollView
          ref={ref}
          automaticallyAdjustContentInsets={false}
          contentInsetAdjustmentBehavior="never"
          {...props}
        />
      );
    });
    
    export default VirtualizedListScrollView;

    Then use it in your list:

    // For FlashList
    <FlashList
      renderScrollComponent={VirtualizedListScrollView}
      {...otherProps}
    />
    
    // For FlatList or LegendList (memoize the component to avoid re-renders)
    const memoList = useCallback(
      (props: ScrollViewProps) => <VirtualizedListScrollView {...props} />,
      [],
    );
    
    <FlatList
      renderScrollComponent={memoList}
      {...otherProps}
    />
  7. Create keyboard animations using hooks

    main

    To create animations synchronized with the keyboard, use either useKeyboardAnimation or useReanimatedKeyboardAnimation. Both hooks return an object containing progress and height values.

    Choosing a hook

    • useKeyboardAnimation: Returns standard React Native Animated.Value objects. It has the Native Driver enabled (useNativeDriver: true), which means it is highly performant but limited to properties that support the native driver (e.g., transform, opacity). It cannot be used to animate properties like height or backgroundColor.
    • useReanimatedKeyboardAnimation: Returns Reanimated.SharedValue objects. This is compatible with React Native Reanimated v2/v3 but is not compatible with the Reanimated v1 API.

    Usage Pattern

    1. Call the hook to get height and progress.
    2. Use progress to interpolate values (e.g., for scaling).
    3. Apply height and interpolated values to Animated.View styles, typically within the transform property to ensure native driver compatibility.
    import React from "react";
    import { Animated, StyleSheet, TextInput, View } from "react-native";
    import { useKeyboardAnimation } from "react-native-keyboard-controller";
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        alignItems: "center",
        justifyContent: "flex-end",
      },
      row: {
        flexDirection: "row",
      },
    });
    
    export default function KeyboardAnimation() {
      // 1. get access to animated values
      const { height, progress } = useKeyboardAnimation();
    
      const scale = progress.interpolate({
        inputRange: [0, 1],
        outputRange: [1, 2],
      });
    
      return (
        <View style={styles.container}>
          <View style={styles.row}>
            <Animated.View
              style={{
                width: 50,
                height: 50,
                backgroundColor: "#17fc03",
                borderRadius: 15,
                // 2. apply transformations
                transform: [{ translateY: height }, { scale }],
              }}
            />
          </View>
          <TextInput
            style={{
              width: "100%",
              marginTop: 50,
              height: 50,
              backgroundColor: "yellow",
            }}
          />
        </View>
      );
    }
  8. Manage multiple KeyboardEffects instances

    main

    Multiple mounted KeyboardEffects instances share a single native keyboard state. They are reconciled using a stack mechanism similar to React Native's StatusBar:

    1. Each instance's translucent value is added to a shared stack.
    2. The most recently mounted (or updated) instance's value wins.
    3. When an instance unmounts, the keyboard falls back to the value of the next-most-recently-mounted instance still on screen, rather than defaulting immediately to opaque.
  9. Migrate from `react-native-reanimated` to `react-native-keyboard-controller`

    main

    Starting from react-native-reanimated@4.2.0, the useAnimatedKeyboard hook is deprecated. To migrate without rewriting your entire codebase, you can use the compatibility layer provided by react-native-keyboard-controller. Simply update your import statements to pull useAnimatedKeyboard and KeyboardState from react-native-keyboard-controller instead of react-native-reanimated.

    -import {useAnimatedKeyboard, KeyboardState} from "react-native-reanimated";
    +import {useAnimatedKeyboard, KeyboardState} from "react-native-keyboard-controller";
  10. Basic setup with KeyboardChatScrollView

    main

    Use KeyboardChatScrollView as a drop-in replacement for ScrollView to create a chat interface where the keyboard automatically pushes messages up when it appears and pulls them back when it hides. Pair it with KeyboardStickyView to keep your input composer at the bottom of the screen.

    import { TextInput, View } from "react-native";
    import {
      KeyboardChatScrollView,
      KeyboardStickyView,
    } from "react-native-keyboard-controller";
    
    function ChatScreen() {
      return (
        <View style={{ flex: 1 }}>
          <KeyboardChatScrollView>
            {messages.map((msg) => (
              <Message key={msg.id} {...msg} />
            ))}
          </KeyboardChatScrollView>
          <KeyboardStickyView>
            <TextInput placeholder="Type a message..." />
          </KeyboardStickyView>
        </View>
      );
    }