12

I have this strange issue, keyboard keeps closing while typing when TextInput is placed inside Child Functional Component. This issue does not exist if TextInput is placed directly under Parent Component. Here is my code

const SignInScreenC = props => {

// define Hook states here    
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [isEmailEmpty,setIsEmailEmpty] = useState(false);
const [isEmailValid,setIsEmailValid] = useState(true);
const [isPasswordEmpty,setIsPasswordEmpty] = useState(false);


/**
 * Called when Sign in is clicked.
 * checks if the form is valid
 */
 const _OnSignInClicked = () => {
   if(_isFormValid()) {
    //make api call
   }
 }

/* Checks if the form is valid
*/
const _isFormValid = () => {
   //reset values 
   setIsEmailEmpty(false);
   setIsEmailValid(true);
   setIsPasswordEmpty(false);

   let isValid = true;
   if(email.trim() === "") {
      setIsEmailEmpty(true);
      isValid = false;
    }
   else if(!AppUtils.isEmailValid(email)) {
      setIsEmailValid(false);
      isValid = false;
   }
   else if(password.trim() === "") {
      setIsPasswordEmpty(true);
      isValid = false;
   }
 return isValid;
}


const SignInForm = () => {
  return (

    <View style={styles.formStyle}>
    <TextInput
       key="email"
       label={Strings.hint_email}
       value={email}
       keyboardType="email-address"                            
       onChangeText={(text)=>  {
           setEmail(text)
           setIsEmailEmpty(false)
           setIsEmailValid(true)
       }}
       style={styles.marginStyle}
       autoCompleteType = "off"
       scrollEnabled = {false}
       autoCorrect={false}
       autoCapitalize={false}/>

       <TextInput
        key="pass"
        value={password}
        secureTextEntry ={true}
        label={Strings.hint_password}
        style={[styles.marginStyle,styles.stylePassword]}
        onChangeText={(text)=> {
             setPassword(text)
             setIsPasswordEmpty(false)}
        }
        theme="light"
        autoCompleteType = "off"
        scrollEnabled = {false}
        autoCorrect={false}
        autoCapitalize={false}/>
        <Button 
            style={styles.loginStyle}
            title = {Strings.login}
            onPressButton = {() => _OnSignInClicked()}/>

    </View>
  );
}

return ( 

    <>

        <ImageBackground source={Images.screen_backgound} style={{width: '100%', 
          height: '100%'}}>
            <View style = {styles.viewOverlaystyle} />
            <ScrollView  contentContainerStyle = {{flexGrow:1}} 
                keyboardShouldPersistTaps={'handled'}>
                <View style={styles.containerStyle}>
                    <SignInForm/>
                </View>
            </ScrollView>
        </ImageBackground>

    </>
 );
}

const styles = StyleSheet.create({
   ....
})

const mapStateToProps = state => ({
   userData : state.userData
});

const mapDispatchToProps = dispatch =>
    bindActionCreators(UserActions, dispatch);

 const SignInScreen = connect(mapStateToProps,mapDispatchToProps) (SignInScreenC)

 export {SignInScreen};

Everything works fine if I paste everything < SignInForm> directly to render method.

mansour lotfi
  • 524
  • 2
  • 7
  • 22
VjLxmi
  • 400
  • 1
  • 4
  • 17
  • 1
    how you are using as child component here change your question with that code.So, we can figure out what you are doing wrong. – Prakash Karena Jan 24 '20 at 09:04

2 Answers2

28

your SignInForm function (which is treated like React component, because its capitalized and called as JSX) is declared inside your SignInScreenC component. This means that every render, new type of React component is created.

  1. SignInScreenC renders first time: creates SignInForm component, instantiates it and renders it
  2. SignInScreenC renders second time: creates another, completely different SignInForm component, instantiates it again, effectively unmounting old SignInForm and rendering new SignInForm in it's place
  3. since old input is unmounted, you lose keyboard focus

This is due to the way React handles rendering: whenever it encounters different type of element that should be rendered in place of an old element, old one will be unmounted. To react, every new SignInForm is different from the previous one as you keep constantly creating new functions

Solutions:

  1. create separate SignInForm component outside of SignInScreenC and pass all the necessary data as props
  2. or, instead of const SignInForm = () => return (...) use const renderSignInForm = () => return (...), and while rendering, instead of <SignInForm/> call it like {renderSignInForm()}. This way it will not be treated like a component and will not be a subject to unmounts
Max
  • 4,473
  • 1
  • 16
  • 18
  • Perfectly explained. This was what i was looking for. Thanks a lot :) – VjLxmi Feb 04 '20 at 13:28
  • I do have an InputText component outside my functional component and passing data and callback function as props but still facing the same problem – hfarhanahmed May 30 '23 at 13:15
1

I had a slightly different but related issue trying to propage a text change to a parent component (React Native).

If your components bubbles up the onChangeText event and that triggers the re-render and ensuing lost of focus on keyboard, you can also consider propagating your change event onEndEditing instead once the user is done inputting text and keep a local state for the text entry.

export function YourTextInputComponent(
  { initialValue, onChangeTextDone } : 
  { initialValue: string, onChangeTextDone : (text: string) => void) }
): JSX.Element {
  const [text, setText] = useState<string>(initialValue);
  
  return (
    <TextInput
      value={text}
      onChangeText={(txt) => {
        setText(txt);
      }}
      onEndEditing={(event) => {
        onChangeTextDone(text);
      }}
    />
  )
}