简体   繁体   中英

React Native Android TextInput issue when using useSafeArea Hook

I've made my own side modal as below code. It accepts a children component which then will be rendered as the content of the Modal. It is working as expected on web and ios But on Android, there's a problem when I pass any children component that has textinput component in them. When I touch the textinput component, the keyboard would come out and disappear immediately and also seems to render the children component again as I see the values going back to initial.

It is very strange because when I pass the exact same code without making it a component, this doesn't happen at all.

Even more strange, my modal is set to be certain width on wider screen and smaller screen renders the modal in full view. The problem only occurs when it is in full view. On Landscape, the keyboard seems little different and doesn't have any issue.

If somebody can please help me, I'll be in great debt.

SideModal.js

import React, { useEffect, useState, useRef } from "react";
import { useSafeArea } from "react-native-safe-area-context";
import { View, Animated, Dimensions } from "react-native";
import { Icon } from "react-native-elements";
import { styles } from "../../styles/globalStyle";
import { TouchableOpacity } from "react-native-gesture-handler";

//This Modal should have below props mandatory
// 1. children component, 2. isVisble, 3. setIsVisble, 4. size, 5, limit
//This Modal should always reside in SafeAreaView

const SideModal = props => {
  const slideOut = useRef(new Animated.Value(0)).current;

  const [dimensions, setDimensions] = useState({
    window: Dimensions.get("window"),
  });

  const insets = useSafeArea();

  let size = 0;
  if (dimensions.window.width < props.limit) {
    size = dimensions.window.width;
  } else {
    size = props.size;
  }

  useEffect(() => {
    Animated.timing(slideOut, {
      toValue: -size,
      duration: 300,
      useNativeDriver: true,
    }).start();
    return function cleanup() {
      Animated.timing(slideOut).reset();
    };
  }, [slideOut]);

  const disappear = () => {
    Animated.timing(slideOut, {
      toValue: 0,
      duration: 300,
      useNativeDriver: true,
    }).start(({ finished }) => {
      props.setIsVisble(null);
    });
  };

  useEffect(() => {
    Dimensions.addEventListener("change", disappear);
    return () => {
      Dimensions.removeEventListener("change", disappear);
    };
  });

  return (
    <View style={[styles.modalContainer]}>
      <View
        onStartShouldSetResponder={() => disappear()}
        style={styles.modalBackDrop}
      >
        <TouchableOpacity
          onPress={() => disappear()}
          style={{ width: "100%", height: "100%" }}
        ></TouchableOpacity>
      </View>
      <Animated.View
        style={[
          styles.modalContent,
          {
            paddingTop: insets.top,
            marginRight: -size,
            width: size,
            transform: [{ translateX: slideOut }],
          },
        ]}
      >
        <Icon
          containerStyle={{
            marginTop: 5,
            marginBottom: -47,
            zIndex: 1,
            alignSelf: "flex-end",
          }}
          iconStyle={{
            marginRight: 10,
            opacity: 0.8,
          }}
          name="clear"
          type="material"
          color="#2E394B"
          size={42}
          onPress={() => disappear()}
        />
        {props.children}
      </Animated.View>
    </View>
  );
};

export default SideModal;

Main.js - direct JSX as children component, no issue

{isVisible == 2 && (
  <SideModal
    isVisible={isVisible}
    setIsVisble={setIsVisble}
    size={400}
    limit={600}
  >
    <View>
      <TextInput />
    </View>
  </SideModal>
)}

Main.js - custom component as children component, yes issue..

const SampleView = () => {
  return(
    <View>
      <TextInput/>
    </View>
  )
};
{isVisible == 2 && (
  <SideModal
    isVisible={isVisible}
    setIsVisble={setIsVisble}
    size={400}
    limit={600}
  >
    <SampleView/>
  </SideModal>
)}

Styles

modalContainer: {
  position: "absolute",
  width: "100%",
  height: "100%",
  justifyContent: "center",
  alignItems: "flex-end",
  overflow: "hidden",
  zIndex: 1,
  elevation: 2,
},
modalBackDrop: {
  width: "100%",
  height: "100%",
  position: "absolute",
},
modalContent: {
  shadowOpacity: 0.75,
  shadowRadius: 10,
  shadowColor: "#cccccc",
  shadowOffset: { height: 0, width: 0 },
  elevation: 10,
  zIndex: 4,
  backgroundColor: "#ffffff",
  height: "100%",
  display: "flex",
},

I have absolutely no idea as to where this problem is coming from.. I'm using Expo SDK for this project so maybe my react-native version could be the problem..?

Please enlighten me!

Environment

  • Testing on 2 Android devices and 1 iphone 6s
  • React Native 0.62
  • Expo SDK 38

Ok, I figured the problem finally.

Please see the below code.

Causes problem

import React from "react";
import { Text, View, TextInput } from "react-native";
import { useSafeArea } from "react-native-safe-area-context";

const Test = () => {
  const insets = useSafeArea();
  const MyInputView = () => {

    return (
      <View>
        <TextInput placeholder="input" />
      </View>
    );
  };

  return (
    <View
      style={{
        width: "100%",
        height: "100%",
        alignItems: "center",
        justifyContent: "center",
        display: "flex",
      }}
    >
      <MyInputView /> // Pass Children Component
    </View>
  );
};

export default Test;

No Problem #1

import React from "react";
import { Text, View, TextInput } from "react-native";
import { useSafeArea } from "react-native-safe-area-context";

const Test = () => {
  const insets = useSafeArea();
  const MyInputView = () => {
    return (
      <View>
        <TextInput placeholder="input" />
      </View>
    );
  };

  return (
    <View
      style={{
        width: "100%",
        height: "100%",
        alignItems: "center",
        justifyContent: "center",
        display: "flex",
      }}
    >
      <View>
        <TextInput placeholder="input" /> //Pass JSX Directly
      </View>
    </View>
  );
};

export default Test;

No Problem #2 - My Choice

import React from "react";
import { Text, View, TextInput } from "react-native";
import { useSafeArea } from "react-native-safe-area-context";

const Test = () => {
  const Wrapper = props => {
    const insets = useSafeArea();
    return (
      <View
        style={{
          width: "100%",
          height: "100%",
          alignItems: "center",
          justifyContent: "center",
          display: "flex",
        }}
      >
        {props.children}
      </View>
    );
  };

  const MyInputView = () => {
    return (
      <View>
        <TextInput placeholder="input" />
      </View>
    );
  };

  return (
    <Wrapper>
      <MyInputView />
    </Wrapper>
  );
};

export default Test;

So I think this is a bug since this is only reproducible on Android device. Could be a bug that has been fixed on the latest react native version actually, but since I'm using Expo, I'm stuck with lower version.

The problem occurs when you declare a variable with useSafeArea() Hook ( or useSafeAreaInsets) in the top level component with in the screen and then when you use a custom component that includes TextInput. Even if you don't use the variable, if it's declared, that's going to cause the problem. If you declare the hook inside a separate component, or just pass the JSX directly as the child component which contains TextInput, the problem is gone. I chose to make a separate wrapper component since the child component had to have it's own props and writing JSX directly really made the code look dirty.

I absolutely have no idea why it does that, but since this issue also resets the TextInput Component, I guess there's some kind of collision between the Hook and Component on Android device.

Hope this saves someone's 3 days if they come across this problem.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM