Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keyboard dismisses while typing TextInput in nested functional component React Native

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.

like image 835
VjLxmi Avatar asked Jan 24 '20 07:01

VjLxmi


2 Answers

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
like image 191
Max Avatar answered Nov 15 '22 04:11

Max


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);
      }}
    />
  )
}
    
like image 26
Pierre-Olivier Charlebois Avatar answered Nov 15 '22 03:11

Pierre-Olivier Charlebois