Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to open keyboard automatically in React Native when component mounts?

My page has only a single TextInput, and I have passed in the autoFocus prop: autoFocus: true.

<TextInput
   style={styles.textInput}
   placeholder="Quiz Deck Title"
   autoFocus={true}
   value={this.state.title}
   onChangeText={(title) => this.controlledTextInput(title)}
/>

What I am trying to avoid is requiring the user to "click" in the TextInput box before the keyboard pops up.

But I would also like to have the soft keyboard also open automatically (if the device does not have a hardware keyboard).

Is there way to make this happen in react native? I am developing for both ios and android.

If it matters, my page is navigated to via TabNavigator.
I mention this because a comment to another similar SO question (see below) suggests they had a similar issue when they arrived at their page using StackNavigator.

Note on Similar SO questions:
How to open keyboard automatically in React Native? : does not provide a solution, and comments by others suggest the same results as myself: input is focused, but keyboard does not automatically open.
Close/hide the Android Soft Keyboard and Android: show soft keyboard automatically when focus is on an EditText : are using native android code (java), not react native code (javascript).

Note: I am developing using the android emulator Nexus 6P with android 23 (as recommended), and ios simulator with iPhone 6s, as I do not have physical devices.

Edit: Adding Requested Code

NewDeck.js (the view I want the keyboard to auto pop up on):

import React from 'react';
import { connect } from 'react-redux';
import { View, Text, TouchableOpacity,
         TextInput, KeyboardAvoidingView,
         StyleSheet, Platform,
       } from 'react-native';

import StyledButton from '../components/StyledButton';

import { saveDeck }      from '../store/decks/actionCreators';
import { getDeckList }   from '../store/decks/selectors';
import { fetchDecks }    from '../utils/api';
import { saveDeckTitle } from '../utils/api';
       } from '../utils/colors';
import { titleCase, stripInvalidChars, makeStringUnique }
  from '../utils/helpers';
import { white, gray, primaryColor, primaryColorDark, primaryColorLight,

class NewDeck extends React.Component {    
  state = {
    title: '',
    canSubmit: false,
  }    
  componentDidMount () {
    this.textInputRef.focus()
  }
  controlledTextInput(title){
    title = titleCase(stripInvalidChars(title));
    const canSubmit = this.isValidInput(title);
    this.setState({ title, canSubmit });
  }    
  isValidInput(text){
    return text.trim() !== '';
  }
  onBlur(){
    title = this.state.title.trim();
    const unique = makeStringUnique(title, this.props.existingTitles);
    this.setState({ title: unique });
  }    
  onSubmit(){
    let title = this.state.title.trim();    
    title = makeStringUnique(title, this.props.existingTitles)
    saveDeckTitle(title)    
    this.props.navigation.navigate('Home');
  }    
  render() {
      return (
          <View style={styles.container}>
            <View style={[styles.cardContainer, {flex: 1}]}>
              <Text  style={styles.instructionsText}
                >
                Title for your New Quiz Deck
              </Text>

              <KeyboardAvoidingView {...keyboardAvoidingViewProps}>
                <TextInput
                  style={styles.textInput}
                  placeholder="Quiz Deck Title"
                  value={this.state.title}
                  onChangeText={(title) => this.controlledTextInput(title)}
                  /* autoFocus={true} */
                  ref={ref => this.textInputRef = ref}
                  />
              </KeyboardAvoidingView>
            </View>

            <KeyboardAvoidingView
              {...keyboardAvoidingViewProps}
              style={[styles.buttonsContainer, styles.buttonContainer]}
              >
              <StyledButton
                style={[styles.item, style={flex: 2}]}
                onPress={() => this.onSubmit()}
                disabled={!this.state.canSubmit}
                >
                <Text>
                  Submit
                </Text>
              </StyledButton>
            </KeyboardAvoidingView>
          </View>
      );
  }
}

const keyboardAvoidingViewProps = {
  behavior: 'padding',
};

// ...styles definition here, - I posted it in a later code block, to minimize 
// clutter, in the event that it is irrelevant to this issue

function mapStoreToProps(store){
  const decks  = getDeckList(store) || null;
  // ensure titles are unique (better UX than if just make id unique)
  const existingTitles = decks && decks.map(deck => {
    return deck.title
  }) || [];
  return {
    existingTitles,
  }
}
export default connect(mapStoreToProps)(NewDeck);

TabNavigator and StackNavigator code (in App.js):

// ... the TabNavigator I'm using:
import { TabNavigator, StackNavigator } from 'react-navigation';

//... the class's render method, uses StackNavigator (as MainNavigation)
render(){
    return (
      <Provider store={createStore(rootReducer)}>
        <View style={{flex:1}}>
          <AppStatusBar
            backgroundColor={primaryColor}
            barStyle="light-content"
            />
          <MainNavigation />
        </View>
      </Provider>
    );
  }
}

// ... 
const Tabs = TabNavigator(
  {
    DeckList: {
      screen: DeckList,
      navigationOptions: {
        tabBarLabel: 'Quiz Decks',
        tabBarIcon: ({ tintColor }) =>  // icons only show in ios
          <Ionicons name='ios-bookmarks' size={30} color={tintColor} />
      },
    },
    NewDeck: {
      screen: NewDeck,
      navigationOptions: {
        tabBarLabel: 'Create New Deck',
        tabBarIcon: ({ tintColor }) => // icons only show in ios
          <FontAwesome name='plus-square' size={30} color={tintColor} />
      },
    },
  },
  {
    navigationOptions: {
      // do-not-display page headers for Tab Navigation
      header: null
    },    
    tabBarOptions: {
      // ios icon and text color; android text color
      activeTintColor:   Platform.OS === 'ios' ? primaryColor : white,    
      pressColor: white,    
      indicatorStyle: {
        backgroundColor: primaryColorDark,
        height: 3,
      },    
      style: {
        height: 56,
        backgroundColor: Platform.OS === 'ios' ? white  : primaryColor,    
        shadowColor: 'rgba(0, 0, 0, 0.24)',
        shadowOffset: {
          width: 0,
          height: 3
        },
        shadowRadius: 6,
        shadowOpacity: 1
      }
    }
  }
);

//... StackNavigator uses TabNavigator (as Tabs)
const stackScreenNavigationOptions = {
  headerTintColor:   white,
  headerStyle: {
    backgroundColor: primaryColor,
  }
};
const MainNavigation = StackNavigator(
  //  RouteConfigs: This is analogous to defining Routes in a web app
  {
    Home: {
      screen: Tabs,  // Which also loads the first Tab (DeckList)
    },
    Deck: {
      screen: Deck,
      navigationOptions: stackScreenNavigationOptions,
    },
    Quiz: {
      screen: Quiz,
      navigationOptions: stackScreenNavigationOptions,
    },
    NewDeck: {
      screen: NewDeck,
      navigationOptions: stackScreenNavigationOptions,
    },
    NewCard: {
      screen: NewCard,
      navigationOptions: stackScreenNavigationOptions,
    },
  },
);

This is the styles definition for NewDeck.js

const styles = StyleSheet.create({
  // CONTAINER styles
  wrapper: {
    // this was the previous container style
      flex: 1,
      backgroundColor: white,
      alignItems: 'center',
      justifyContent: 'center',
    },
  container: {
    flex: 1,
    backgroundColor: white,
    alignItems: 'center',
    justifyContent: 'space-between',

    padding: 10,
    paddingTop: 30,
    paddingBottom: 5,
  },
  cardContainer: {
    flex: 1,
    justifyContent: 'flex-start',
    alignSelf: 'stretch',
    backgroundColor: '#fefefe',

    padding:     20,
    marginLeft:  30,
    marginRight: 30,
    marginTop:   10,
    borderRadius: Platform.OS === 'ios' ? 20 : 10,

    shadowRadius: 3,
    shadowOpacity: 0.8,
    shadowColor: 'rgba(0, 0, 0, 0.24)',
    shadowOffset: {
      width: 0,
      height: 3,
    },
    marginBottom:20,
  },
  buttonsContainer: {
    flex: 3,
    alignSelf: 'stretch',
    justifyContent: 'flex-start',
  },
  buttonContainer: {
    justifyContent: 'center',
    margin: 10,
  },

  // TEXT Styles
  instructionsText: {
    flex: 1,
    fontSize: 20,
    color: gray,

    alignSelf: 'center',
    textAlign: 'center',
  },

  // INPUTTEXT styles
  textInput: {
    fontSize: 27,
    color: primaryColor,

    alignSelf: 'stretch',
    flexWrap:  'wrap',
    textAlign: 'center',
    marginTop: 10,
  },
});

StyledButton.js (Basically, TouchableOpacity with platform-specific styling, for universal use across the app):

import React from 'react';
import { Text, TouchableOpacity, StyleSheet, Platform } from 'react-native';
import { white, gray, primaryColor, primaryColorLight, primaryColorDark} from '../utils/colors';

export default function TextButton({ children, onPress, customColor, disabled=false }) {
  const disabledColor = disabled ? gray : null;
  const backgroundColor = Platform.OS==='ios' ? white : disabledColor || customColor || primaryColorLight;
  const borderColor     = Platform.OS==='ios' ? disabledColor || customColor || primaryColorDark
  const textColor       = Platform.OS==='ios' ? disabledColor || customColor || primaryColor  : white;
  const btnStyle = Platform.OS==='ios' ? styles.iosBtn : styles.androidBtn;
  const txtStyle = styles.txtDefault;
  const btnColor = { backgroundColor, borderColor };
  const txtColor = { color: textColor };

  return (
      <TouchableOpacity
        onPress={onPress}
        disabled={disabled}
        style={[btnStyle, btnColor]}
        >
        <Text
          style={[styles.txtDefault, txtColor]}
          >
          {children}
        </Text>
      </TouchableOpacity>
  );
}

const styles = StyleSheet.create({
  txtDefault: {
    textAlign: 'center',
    // because of bleeding of white text to colored background on android,
    // enlarge text (or increase fontWeight) for better readability
    fontSize: Platform.OS==='ios' ? 15 : 18,
    padding: 10,
  },
  iosBtn: {
    height: 45,
    borderRadius: 7,
    alignSelf:      'center',
    justifyContent: 'center',
    alignItems:     'center',
    // ios only settings
    borderColor:   primaryColorDark,
    borderWidth:   1,
    borderRadius:  3,
    paddingLeft:  25,
    paddingRight: 25,
  },
  androidBtn: {
    height: 45,
    borderRadius: 5,
    alignSelf:      'center',
    justifyContent: 'center',
    alignItems:     'center',
    // android- only settings
    // (padding accepts clicks, vs. margin === no-click zone)
    padding: 20,
    paddingLeft:  15,
    paddingRight: 15,
  },
});

// ios has white buttons with colored outlines and colored text
// android has colored buttons with white text
// Pass in a button color, or it defaults to the App's primary colors
like image 529
SherylHohman Avatar asked Apr 23 '18 20:04

SherylHohman


2 Answers

just wrap the ref inside timeout setTimeout(()=>{this.textInputRef.focus()},100)

like image 92
Mugi Wiguna Avatar answered Nov 04 '22 16:11

Mugi Wiguna


Old Issue, but if anyone is searching through here for an answer..

It looks like the keyboard doesn't come up if you're stuck in an animation (e.g. if coming from another screen).

You can use a ref to focus on your input, but must wrap in within InteractionManager.runAfterInteractions for it to work correctly.

This is how I solved it:

export const CustomInput: FunctionComponent<Props & TextInputProps> = ({
  error,
  ...props
}) => {
  const inputRef = useRef<TextInput>(null);

  useEffect(() => {
    // Must run after animations for keyboard to automatically open
    InteractionManager.runAfterInteractions(() => {
      if (inputRef?.current) {
        inputRef.current.focus();
      }
    });
  }, [inputRef]);

  return (
    <View>
      <TextInput
        ref={inputRef}
        {...props}
      />
      {error && <ErrorText>{error}</ErrorText>}
    </View>
  );
};```
like image 26
Jackson Avatar answered Nov 04 '22 16:11

Jackson