6

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
David Schumann
  • 9,116
  • 6
  • 56
  • 78
SherylHohman
  • 12,507
  • 16
  • 70
  • 78
  • If you've only tested on the simulators then you might just have the keyboard hidden by default. In the ios simulator, hit cmd + k to turn it on. – Matt Aft Apr 23 '18 at 20:43
  • @MattAft - yes, that I *did* happen to find. I'm glad you mentioned it for future visitors, as that is not an obvious requirement. – SherylHohman Apr 23 '18 at 20:58
  • @MattAft On both platforms currently, the user is required to "click" in the text field before the keyboard will show up. I find this an annoying feature when I use apps that behave like this. What I would prefer, is that the keyboard automatically pops up when the page loads. – SherylHohman Apr 23 '18 at 20:58
  • You can add focus to any `textinput` when the component loads to show the keyboard using it's reference – Pritish Vaidya Apr 23 '18 at 21:07
  • 1
    @MattAft I misspoke: The keyboard does not automatically pop up in *Android*, but it does pop up in iOS (cmd-k is on). – SherylHohman Apr 23 '18 at 21:14
  • @SherylHohman Hmm the keyboard should always auto-show when a textInput is focused regardless of platform, not sure if it's also hidden by default on android but maybe look into the settings for the emulator – Matt Aft Apr 23 '18 at 21:20
  • @MattAft, yes great suggestion, unfortunately I could not find such setting for android.. – SherylHohman Apr 23 '18 at 21:23

4 Answers4

4

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

3

You can always use .focus on any of your TextInput when the component loads, to show the keyboard if you want to avoid the autoFocus.

componentDidMount () {
    this.textInputRef.focus()
}

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

As mentioned in the docs

Two methods exposed via the native element are .focus() and .blur() that will focus or blur the TextInput programmatically.

David Schumann
  • 9,116
  • 6
  • 56
  • 78
Pritish Vaidya
  • 18,941
  • 3
  • 43
  • 67
  • 2
    Unfortunately, that is giving me the same behavior: user must "click" in the input box before keyboard will pop up. – SherylHohman Apr 23 '18 at 21:29
  • Ok, when you load the same component, is the `textInput` focused or is it still `unfocused`? – Pritish Vaidya Apr 23 '18 at 21:31
  • hmm.. good question. The cursor is not showing inside the TextInput box (until I click inside the TextInput), so I suspect it is not actually "focusing". This is the same, both for your code, and my posted code. – SherylHohman Apr 23 '18 at 21:33
  • There might be some `overlay` or something else is using the `ref focus`. – Pritish Vaidya Apr 23 '18 at 21:34
  • When you use autofocus, it basically calls focus once the component mounts so it's the same exact thing. If you type and it shows up on the textinput then you'll know it's focused. – Matt Aft Apr 23 '18 at 21:37
  • It is wrapped in `KeyboardAvoidingView`, and there is a `Text`, and a `TouchableOpacity` (Submit Button) component on the page. That's all. Could `KeyboardAvoidingView` be interfering ? – SherylHohman Apr 23 '18 at 21:41
  • You can update the question with the relevant code, also which `TabNavigator` are you using? – Pritish Vaidya Apr 23 '18 at 21:43
  • Ok, then. No, it's not focused until I click in the `TextInput` box. When I use my keyboard to type, nothing shows unless I've already clicked within the input box. – SherylHohman Apr 23 '18 at 21:43
  • Ok, posted TabNavigator code, and NewDeck code. `NewDeck.j`s has the `TextInput` that I want to autoFocus, and have the keyboard automatically pop up on when the component mounts. I left out `import` statements and my `styles` definitions, but can them add it if you think they may be relevant. – SherylHohman Apr 23 '18 at 22:23
  • I went ahead and added `styles`, imports and `StylesButton` code, so they are available to you whenever you find time to peruse the code. Thanks for your help so far. – SherylHohman Apr 23 '18 at 23:21
  • Hey @SherylHohman, Were you able to find a way to do this? I am facing similar issue like yours. – Amit Kumar Apr 08 '20 at 05:15
3

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>
  );
};```
SherylHohman
  • 12,507
  • 16
  • 70
  • 78
Jackson
  • 31
  • 2
0

I only needed the 'autoFocus' prop to get this going as at today.

https://reactnative.dev/docs/textinput#autofocus

Mayowa Daniel
  • 139
  • 1
  • 7