1

I have a functional component Login Screen and have 4 Input Fields in them, along with a login button. Here is the Full Code for my component Screen:

export default function Login() {
  //Configs
  const navigation = useNavigation();
  const orientation = useDeviceOrientation();
  const { colors, dark } = useTheme();

  const InputTheme = {
    colors: {
      placeholder: colors.accent,
      primary: colors.accent,
      error: "red",
    },
  };

  /** State Codes */
  //States
  const [login, setLogin] = useState({
    email: "",
    password: "",
    licenseKey: "",
    deviceName: "",
  });
  const [loading, setLoading] = useState(false);
  const [secureEntry, setSecureEntry] = useState(true);

  //Errors
  const [errorEmail, setEmailError] = useState(false);
  const [errorPWD, setPWDError] = useState(false);
  const [errorLicense, setLicenseError] = useState(false);
  const [errorDevice, setDeviceError] = useState(false);

  //Error Messages
  const [messageEmail, setEmailMessage] = useState("Looks Good");
  const [messagePWD, setPWDMessage] = useState("All Good");

  async function VerifyInputs() {
    var pattern = /^[a-zA-Z0-9\-_]+(\.[a-zA-Z0-9\-_]+)*@[a-z0-9]+(\-[a-z0-9]+)*(\.[a-z0-9]+(\-[a-z0-9]+)*)*\.[a-z]{2,4}$/;
    if (login.email == "") {
      //Email cannot be empty
      setEmailMessage("Email cannot be Blank!");
      setEmailError(true);
      return;
    } else if (login.email != "" && !pattern.test(login.email)) {
      //Email is not valid
      setEmailMessage("This is not a valid email address!");
      setEmailError(true);
      return;
    } else {
      console.log("resolved email");
      setEmailMessage("");
      setEmailError(false);
    }
    if (login.password == "") {
      //Password cannot be empty
      setPWDMessage("Password cannot be Empty!");
      setPWDError(true);
      return;
    } else if (login.password.length < 5) {
      //Password must be minimum 5 characters.
      setPWDMessage("Password must be of minimum 5 characters!");
      setPWDError(true);
      return;
    } else {
      console.log("resolved password");
      setPWDMessage("");
      setPWDError(false);
    }
    if (login.licenseKey == "") {
      //License Key can't be Empty
      setLicenseError(true);
      return;
    } else {
      console.log("License resolved");
      setLicenseError(false);
    }
    if (login.deviceName == "") {
      //Device Name can't be empty as well
      setDeviceError(true);
      return;
    } else {
      console.log("Device name resolved");
      setDeviceError(false);
    }
    Toast.show("Validation Successful");
  }

  function MobileContent() {
    console.log("mobile_content rerendered");
    return (
      <View style={{ flex: 1, backgroundColor: colors.accent }}>
        <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}></View>
        <View style={{ flex: 3, justifyContent: "center" }}>
          {/**Main Content */}
          <View style={[styles.content, { backgroundColor: colors.primary }]}>
            <View style={{ flex: 1 }}>
              {/**For Header */}
              <Header />
            </View>
            <View style={{ flex: 5 }}>
              {/**For Content */}
              <ScrollView style={{ flex: 1 }}>
                <LoginContent />
              </ScrollView>
            </View>
            <View style={{ flex: 1 }}>
              {/**For Footer */}
              <Footer />
            </View>
          </View>
        </View>
      </View>
    );
  }

  function TabContent() {
    console.log("tab_content rerendered");
    return (
      <View
        style={{
          flex: 1,
          backgroundColor: colors.accent,
          flexDirection: orientation.landscape ? "row" : "column",
        }}>
        <View
          style={{
            flex: 1,
            justifyContent: "center",
            alignItems: "center",
          }}></View>
        <View style={{ flex: 1.5, justifyContent: "center" }}>
          {/**Main Content */}
          <View style={[styles.content, { backgroundColor: colors.primary }]}>
            {/**Header Wrapper */}
            <View style={{ justifyContent: "center" }}>
              {/**Header Title */}
              <Header />
            </View>
            {/**Content Wrapper */}
            <LoginContent />
            {/**Footer Wrapper */}
            <View style={{ justifyContent: "center" }}>
              {/** Login Button */}
              <Footer />
            </View>
          </View>
        </View>
      </View>
    );
  }

  function Header() {
    console.log("header_component rerendered");
    return (
      <View style={{ margin: "5%" }}>
        <Title>Welcome User</Title>
        <Subheading>Please Sign In to Continue..</Subheading>
      </View>
    );
  }

  function Footer() {
    console.log("footer_component rerendered");
    return (
      <View style={{ margin: isTablet ? "5%" : "3.5%" }}>
        <Button
          title="Login"
          loading={false}
          ViewComponent={LinearGradient}
          containerStyle={{ maxWidth: isTablet ? "45%" : "100%" }}
          buttonStyle={{ height: 50, borderRadius: 10 }}
          linearGradientProps={{
            colors: [colors.accent, colors.accentLight],
            start: { x: 1, y: 1 },
            end: { x: 1, y: 0 },
          }}
          onPress={() => {
            VerifyInputs();
            //navigation.navigate("verify");
          }}
        />
      </View>
    );
  }

  function LoginContent() {
    console.log("login_component rerendered");
    return (
      <View style={{ margin: "3%" }}>
        {/**Login & Email Wrapper */}
        <View style={{ flexDirection: isTablet ? "row" : "column" }}>
          <View style={styles.input}>
            <TextInput
              mode="outlined"
              label="Email"
              value={login.email}
              error={errorEmail}
              theme={InputTheme}
              onChangeText={(text) => setLogin({ ...login, email: text })}
            />
            {errorEmail ? (
              <HelperText visible={true} type="error" theme={{ colors: { error: "red" } }}>
                {messageEmail}
              </HelperText>
            ) : null}
          </View>
          <View style={styles.input}>
            <View style={{ flexDirection: "row", alignItems: "center" }}>
              <TextInput
                mode="outlined"
                label="Password"
                value={login.password}
                error={errorPWD}
                theme={InputTheme}
                secureTextEntry={secureEntry}
                style={{ flex: 1, marginBottom: 5, marginEnd: isTablet ? 15 : 5 }}
                onChangeText={(text) => setLogin({ ...login, password: text })}
              />
              <Button
                icon={
                  <Icon
                    name={secureEntry ? "eye-off-outline" : "eye-outline"}
                    size={30}
                    color={colors.primary}
                  />
                }
                buttonStyle={{
                  width: 55,
                  aspectRatio: 1,
                  backgroundColor: colors.accent,
                  borderRadius: 10,
                }}
                containerStyle={{ marginStart: 5 }}
                onPress={async () => setSecureEntry(!secureEntry)}
              />
            </View>
            {errorPWD ? (
              <HelperText visible={true} type="error" theme={{ colors: { error: "red" } }}>
                {messagePWD}
              </HelperText>
            ) : null}
          </View>
        </View>
        {/**License & Device Wrapper */}
        <View style={{ flexDirection: isTablet ? "row" : "column" }}>
          <View style={styles.input}>
            <TextInput
              mode="outlined"
              label="License Key"
              value={login.licenseKey}
              error={errorLicense}
              theme={InputTheme}
              onChangeText={(text) => setLogin({ ...login, licenseKey: text })}
            />
            {errorLicense ? (
              <HelperText visible={true} type="error" theme={{ colors: { error: "red" } }}>
                License Key cannot be Empty!
              </HelperText>
            ) : null}
          </View>
          <View style={styles.input}>
            <TextInput
              mode="outlined"
              label="Device Name"
              value={login.deviceName}
              error={errorDevice}
              theme={InputTheme}
              onChangeText={(text) => setLogin({ ...login, deviceName: text })}
            />
            {errorDevice ? (
              <HelperText visible={true} type="error" theme={{ colors: { error: "red" } }}>
                Device Name cannot be empty
              </HelperText>
            ) : null}
          </View>
        </View>
      </View>
    );
  }

  return (
    <SafeAreaView style={styles.container}>
      <StatusBar barStyle={"dark-content"} backgroundColor={colors.accent} />
      {isTablet ? <TabContent /> : <MobileContent />}
    </SafeAreaView>
  );
}

As you can see by looking at the code.. my Login component's rendering responsive component layouts based on device type being phone or tablet, the following components MobileContent or TabContent.

MobileContent functional component wraps my main content in a ScrollView & TabContent functional component doesn't need a scrollview but has responsive scaling views.

Both of these parent components display my common components, Header,LoginContent & Footer.

My Header component just displays a standard title & subheading. My LoginContent component has all the TextInput fields in them with validation logic setup as well. My Footer component has the Submit/Login button. That's All.

So we can summarize the components tree for screen as:

Login Screen/Parent Component => Mobile Content/Tab Content => Header, LoginContent & Footer (Common Components)

Alright, so now what is the issue? the major issue for me lies for TextInputs in LoginContent component, but I assume the underlying issue is applicable for entire Login Screen component.

The ISSUE: When I click on TextInput fields, the input gets focus and Keyboard appears. Everything is fine till this point. As soon as I type even a single letter, single key press on keyboard.. The Keyboard closes immediately and the text input is lost focus.

Why it could be happening? I believe this might be the classic 'React Component Re-render' problem, which causes by TextInput to be re-rendered when state is updated by the onChangeText for the TextInput and hence it loses focus & that's why the Keyboard closes.

So, I hope to resolve atleast first issue from these two issues.

Issue 1: How do I prevent the Keyboard from constantly closing when I try to type something in any of the TextInput fields.

Issue 2: How can I better optimize my Parent & Children components rendering on every state update using the any of these two React Hooks useCallback() and useMemo()?

I've read the documentation for these two hooks useCallback & useMemo multiple times and still haven't grasped the concept of these hooks. All posted examples deal with a Counter Exmaple optimized using useCallback or useMemo but I'm not working with Counters here in my Screen.

I need a more practical exmaple of useCallback() & useMemo() in screen components. Perhaps an implementation of these two hooks in my current Login Screen component will help me understand & grasp the concept better.

Like I mentioned, solving Issue 1 is my highest priority atm, but I know a solution for Issue 2 will help me in the long run as well. It will be very helpful if you can resolve both of my issues here. Thanks for helping.

Rishabh More
  • 85
  • 4
  • 11
  • I found something, Please have look to this : https://stackoverflow.com/a/60048240/12329984 – Sumit Sharma2 Nov 12 '20 at 16:49
  • @SumitSharma2 That is not a valid solution in my case. I've tried the answer from that question and the keyboard's still gets hidden on every key press – Rishabh More Nov 13 '20 at 05:18

0 Answers0