React-native: Is there a way to keep the keyboard open even when input loses focus/prevent blur

Created on 19 Nov 2015  路  48Comments  路  Source: facebook/react-native

I am trying to achieve the same effect as in the native 'notes' app. Basically, I have a text input which covers 95% of the screen and I have a small button at the button. In order to tap that button, I need to tap once in order to hide the keyboard and then only my second tap actually triggers the button's on press event. Any ideas?

Locked

Most helpful comment

@eyaleizenberg - as @ide suggests, keyboardShouldPersistTaps on ScrollView should do it, eg: https://rnplay.org/apps/--p-TQ - let me know if this doesn't fix the problem and I will reopen the issue :)

All 48 comments

Can you put up a screenshot of what you're talking about - I'm having trouble visualizing it. Also, is this iOS or Android?

I think @eyaleizenberg means blurOnSubmit of TextInput

If true, the text field will blur when submitted. The default value is true.

It should work the same as textFieldShouldEndEditing on iOS, but when I tap the done button on the lower-right of keyboard, the TextInput gets blurred and the keyboard dismissed, I've set blurOnSubmit to true, but it seems not working as I expected, is this a bug? @nicklockwood

@matth3wga0 - you want blurOnSubmit to be false

@brentvatne Sorry about the typo, actually I set blurOnSubmit to false, here's the code runs in the iOS Simulator, the keyboard gets dismissed when I tap the Next button.

<TextInput 
    style={style.textField} 
    underlineColorAndroid="transparent"
    blurOnSubmit={false} 
    autoFocus={false} 
    autoCorrect={false} 
    autoCapitalize="none" 
    keyboardType="email-address" 
    returnKeyType="next" 
    onSubmitEditing={this.submit.bind(this)}
/>

Here's the keyboard configuration.

screen shot 2015-11-20 at 15 39 56

What I mean is this:
2tapkeyboard

@eyaleizenberg You have to use View instead of ScrollView.

But I need my view to be a scrollview because there are gonna be other elements there except of the text which need to scroll with it.

There's a prop on ScrollView that tells it to dismiss the keyboard when tapped. Maybe try playing around with that?

@eyaleizenberg - as @ide suggests, keyboardShouldPersistTaps on ScrollView should do it, eg: https://rnplay.org/apps/--p-TQ - let me know if this doesn't fix the problem and I will reopen the issue :)

@brentvatne I am still unable to do this with a ScrollView. I now have the same issue also in a normal View element. What am I missing?

@brentvatne Sorry, my bad about it being a regular View. My clicks on a ListView really didn't hide the keyboard. However clicking on another View, did remove the keyboard.

About the first issue I do think that it's related to the fact that the TextInput is within this same ScrollView. Suggestions?

I'm having this same issue. The basic problem: I want the scrollview to dismiss the keyboard when touched, but I don't want it to 'cancel' or 'prevent default' the user's touch. In other words, I don't want the user to have to 1) touch a <TouchableHighlight> to close the keyboard, and 2) again touch the <TouchableHighlight> for the resulting action to occur. The initial touch should perform both (1) and (2) simultaneously.

@bmcmahen explains the issue correctly. Sometimes the user needs to interact with elements on the page while the keyboard stays open, e.g. tapping a TouchableOpacity component. Right now it takes two taps and is a really jarring user experience.

Exactly what @sjmueller said!

After investigating this issue further, I'm pretty convinced we need something like keyboardShouldPersistTaps on all <Touchable*> views. Right now I have resolved to wrapping some of my tappable buttons inside of ScrollViews in order to get the desired behavior. This has the terrible side effect of making the button scrollable, even when confined to a certain width and height. Furthermore, keyboardShouldPersistTaps is ineffective when the target ScrollView is wrapped within another ScrollView. Not sure if the last part is fixed with 0.17 (since I'm on still 0.15), but either way this feels like the hackiest of hacks. If there is a better way, please share because I've tried just about everything.

@sjmueller You can add scrollEnabled={false} and then the ScrollView won't scroll. However, I agree, all the Touchable* should be able to get keyboardShouldPersist...

I think it might be better to make this a feature of textInput rather than of touchable.

Focus works differently in iOS than on the Web - buttons in iOS can't accept focus, only keyboard input fields, so normally this isn't an issue.

By default, tapping a button on iOS doesn't dismiss the keyboard. In RN, we implemented a common-but-not-standard system of blurring textinput when tapping another control, but this should probably be optional.

@nicklockwood the larger problem here is not around keyboard dismissal, but rather touchable components that don't respond to a tap until the keyboard dismisses. I can't think of a case where this is ever the intended behavior; if a user clicks on a button (or any area known to respond to taps), then that area should respond to the interaction, not discard.

@brentvatne As you can see it's still a major issue... Any advice?

+1 for this issue and preference for the solution as described by @nicklockwood

+1

+1

Anyone looking for a temporary work around on this issue specifically as it relates to undesired keyboard dismissal in a scroll view and the "two taps" issue described by @eyaleizenberg, see the code below. In ScrollResponder.js (line 182) make the function "scrollResponderHandleStartShouldSetResponderCapture" return false.

  scrollResponderHandleStartShouldSetResponderCapture: function(e: Event): boolean {
    // First see if we want to eat taps while the keyboard is up
    // var currentlyFocusedTextInput = TextInputState.currentlyFocusedField();
    // if (!this.props.keyboardShouldPersistTaps &&
    //   currentlyFocusedTextInput != null &&
    //   e.target !== currentlyFocusedTextInput) {
    //   return true;
    // }
    // return this.scrollResponderIsAnimating();
    return false;
  },

Thanks! This worked for me.

@jmeyers44 Worked for me too. Is there actually any other way to close the keyboard without pressing 'Search' as my keyboard show with the code modification made?

@jmeyers44 yes, you can call blur() on the currently focussed TextInput to hide the keyboard. I don't think we have a way to find out which component that is though, so you'll need to keep track of it yourself.

@nicklockwood Is there a way to know when the keyboard has closed so that I can run a callback function or update a state afterwards?

Update: Cracked it :)

This also worked for my problem with ListView. When I had buttons in a ScrollView or ListView it would take 2 taps to register an onPress. I made a slight change to @jmeyers44 code snippet to make it a little less destructive.

In the ScrollResponder.js (~ line 182) file add the first if statement:

  scrollResponderHandleStartShouldSetResponderCapture: function(e: Event): boolean {
    if (this.props.fixDoubleTapIssue) return false;

    // First see if we want to eat taps while the keyboard is up
    var currentlyFocusedTextInput = TextInputState.currentlyFocusedField();
    if (!this.props.keyboardShouldPersistTaps &&
      currentlyFocusedTextInput != null &&
      e.target !== currentlyFocusedTextInput) {
      return true;
    }
    return this.scrollResponderIsAnimating();
  },

Now you can use my (not so well named) property:

<ScrollView style={styles.container}
      fixDoubleTapIssue={true}
      >

The use is the same on the ListView:

<ListView style={styles.container}
      dataSource={this.state.dataSource}
      fixDoubleTapIssue={true}
      renderRow={ (rowData) => this.renderRow(rowData) } />

Hi!

Also faced with the same problem.
Have more flexible solution:

You need to add ref to your scrollView and touchable component (in this example buttonNext) that you want work when keyboard is open.
In view ComponentDidMount method you can wrap original scrollView scrollResponderHandleStartShouldSetResponderCapture method and return false if you want to pass click to selected element. In this case dispatchMarker is a string representing full components hierarchy.

componentDidMount() {
    var scrollViewPressHandler = this.refs.scrollView.scrollResponderHandleStartShouldSetResponderCapture;
    this.refs.scrollView.scrollResponderHandleStartShouldSetResponderCapture = e => {
        if (e.dispatchMarker.indexOf('buttonNext') > -1) {
            return false;
        }
        return scrollViewPressHandler(e);
    }
}

Just found that SmartScrollView handles all this stuff perfectly!
https://github.com/jrans/react-native-smart-scroll-view

@eyaleizenberg Did you ever come up with a solution to this?

keyboardShouldPersistTaps={true} worked for me and I'm using a ScrollView

Yeah, I ended up putting it in a ScrollView as well. I had been hoping for a non-ScrollView solution.

Any consistent workaround?

When keyboardShouldPersit = {true}, it's impossible to unfocus the TextField no matter what, when it's false there is still the double tap problem described before and modifying ScrollResponder.js is not a viable solution.

@mlumbroso Wrap your ScrollView with a TouchableWithoutFeedback (I just wrapped my entire view with it, so that it works everywhere on the screen) and call dismissKeyboard onPress. You have to require dismissKeyboard (built into RN, just not in the docs for some reason) and you have to make sure there is space around your TextInputs so you can press something that isn't a TextInput.

This way, anything that is tapped that isn't a TextInput calls dismissKeyboard which in turn removes any TextInput focus.

Cool, seems a little hackish, but incredibly less than modifying scrollResponder.js file. Will test it tonight and update :-)

Hopefully, RN team will come with a consistent solution for this issue

Hey again, I tried what you suggested to no avail.
Still the same behaviour, with keyboardShouldPersit = {true} no dismissKeyboard triggered, but other elements are clickable directly, and with {false} , needs one tap to remove keyboard and another one to trigger onPress on another element.

Edit : After experimenting a little, the solution is to add the dismissKeyboard on every children onPress callbacks. Thanks for pointing me in the right direction :-)

I still be same problem with the view structure:
View
------View
-----------TextInput
-----------TouchableOpacity onPress = {doAction}
------View
View

I need 2 tap to call doAction when the keyboard is show.

View
------View
-----------TextInput
-----------ListView ,which is visible when TextInput focused,and hidden when user chooses one item for TextInput
-----------TouchableOpacity onPress = {doAction}
-----------TouchableOpacity onPress = {doAction}
------View
View

I am hoping for a non-ScrollView solution.

@philipshurpik is that solution still working for you? I've tried it with no luck. I'm not sure you can overwrite a mixin function like that...I'd love to be able to do something like that, though, because I don't want to dip into ScrollResponder.js and none of the other solutions have worked for me.

I have faced with same issue and found that keyboardShouldPersistTaps='always' does't work because I set it only for inner ScrollView, but I also use react-native-scrollable-tab-view component that has ScrollView inside. So I set contentProps={{keyboardDismissMode: 'interactive', keyboardShouldPersistTaps: 'handled'}} and it worked. So I think you need to set keyboardShouldPersistTaps='always' for every ScrollView in tree.

Just want to chime in here with my case where I had a simple Modal pop up asking for a user's InputText with a save Button on the Modal. The main view was wrapped in a ScrollView as it was a long form.

Without changing anything the user would have to click on the TextInput, enter some text, click anywhere (including the Button) to dismiss the keyboard and then finally click on the save Button to save the result which also dismisses the Modal.

By setting the ScrollView prop keyboardShouldPersistTaps="handled" allowed the user to enter text into the TextInput and click on the save Button, which saved the input and dismisses the Modal all in one go. Perfect.

If I want to to Show an key board Capitalized letters how can i Show it in react-native android this is my code<TextInput underlineColorAndroid="transparent" placeholderTextColor="#a9abad" placeholder="Contact Person" returnKeyType='next' maxLength={25} style={styles.input} value={this.state.AddAddress.contactPerson} ref = 'fourthInput' autoCapitalize="none" onChangeText={this.handleChange.bind(this,'contactPerson')} onSubmitEditing={(event)=>{ this.refs.fifthInput.focus(); }} />

Has anyone gotten this to work with a ListView that contains clickable eliments? I have a search box at the top and can not get it to loose focus and click in the ListView row in one click.

@antonKalinin This is definitely correct. If you have multiple ScrollViews or ListViews layered in your app then you have the possibility of tapping one of them, which means each one can register a Scroll Responder event and close the Keyboard.

@dsandor & @jmeyers44 this is the correct place to test this problem.

scrollResponderHandleStartShouldSetResponderCapture: function(e: Event): boolean {
    // First see if we want to eat taps while the keyboard is up
    var currentlyFocusedTextInput = TextInputState.currentlyFocusedField();
    var {keyboardShouldPersistTaps} = this.props;
    var keyboardNeverPersistTaps = !keyboardShouldPersistTaps ||
                                    keyboardShouldPersistTaps === 'never';
    if (keyboardNeverPersistTaps &&
      currentlyFocusedTextInput != null &&
      !isTagInstanceOfTextInput(e.target)) {
      return true;
    }
    return this.scrollResponderIsAnimating();
  },

BUT you shouldn't need to edit this code if you add keyboardShouldPersistTaps = 'always' to all of your scrollable views, bc what happens with this code is if the prop is not set then keyboardShouldPersistTaps = undefined, which turns into keyboardNeverPersistTaps = true.
In my case I did want the Keyboard to close when dragging the scrollable view, so I also needed to add keyboardDismissMode = {'on-drag'} to all of the scrollable views.

A combination of @antonKalinin and @charlle's solutions worked very well for me (in my case just a single <ScrollView /> element so a simple use case).

<ScrollView
  keyboardShouldPersistTaps="always"
  keyboardDismissMode="on-drag"
  ...
>
  {/* Content containing interactive elements such as <Touchable* /> */}
</ScrollView>

Meant that pressing interactive elements on the screen while the keyboard is open did not close it, but as soon as the user drags the screen it closes.

It is important to note that a ListView is a form of ScrollView and also takes the property keyboardShouldPersistTaps='always'

Was this page helpful?
0 / 5 - 0 ratings