Tabbing Through Input Fields

Kevin Scott
React Native Cafe
Published in
3 min readMay 5, 2017
Photo by Galymzhan Abdugalimov

On the web, it’s common to tab through forms, an intuitive and UX-friendly pattern. You get this out of the box with web forms, but when building apps with React Native, you need to implement this functionality yourself. Fortunately, it’s a cinch to set up.

Native Form UX vs. Web Form UX

First, let’s understand what native UX we’re trying to emulate on React Native.

Here’s a video of navigation through the native iOS contacts app:

And here’s a video of navigation through a Web form:

In summary, the iOS web browser gives us next and previous buttons, but for a native iOS app, these aren’t present, and React Native doesn’t support them natively, either.

I believe the reason for this discrepancy is that, natively, the “return” key performs double duty, tabbing through the form and submitting once the form is complete. On the web, the “return” key will submit the form by default, necessitating the next/previous buttons.

We’ll focus on emulating the native functionality, relying on the “return” key to tab through the form and submit it when complete.

Keyboards and TextInput on React Native

We’ll be using TextInput and View from the react-native library, like so:

import {
TextInput,
View,
} from 'react-native';

Each TextInput defines its own keyboard that appears when focused. This allows a particular input field to specify numeric, numpad, or a number of different options.

TextInputs are also responsible for determining which input to send focus to next, and they provide a handy prop for implementing this.

Capturing the field reference

The first thing we’ll need is to capture the ref of a particular input field.

If you’re not familiar, a ref is a reference to the React component. It’s best practice to specify a callback function and capture the referenced component from the arguments.

In our example, we’re storing each TextInput‘s ref on an internal inputs object we’ll define in the constructor. We specify a custom index we’ll use later to focus on the input.

<TextInput  ref={ input => {    this.inputs['one'] = input;  }}  .../>

Since the ref is defined in the render function, don’t store the reference with setState; doing so will cause an infinite loop and many tears will be shed.

Triggering focus

Next, we need to focus on the next element. We do that by hooking into the onSubmitEditing prop and supplying it with a custom focus function on the component.

onSubmitEditing={() => {  // specify the key of the ref, as done in the previous section.  this.focusNextField('next-field');}}

Then, we set up the field. If we zoom out to the component level:

class App extends React.Component {  constructor(props) {    super(props);    this.focusNextField = this.focusNextField.bind(this);    // to store our input refs    this.inputs = {};  }  focusNextField(key) {    this.inputs[key].focus();  }  ...}

Two things to point out:

  • We need to bind the focus function to the class so we have an accurate reference to this. This is generally done in the constructor.
  • The focus action accepts a key indicating which input to focus on. That key matches what we use in the ref callback above.

Avoiding the disappearing keyboard

Sometimes as we’re tabbing between fields, the keyboard will disappear and reappear. We can avoid this by using a prop on TextInput called blurOnSubmit:

<TextInput  blurOnSubmit={ false }  .../>

This property forces the keyboard to remain visible. Since we’re immediately tabbing to our next field, this behavior works nicely for us.

Return key

Updating the return key to match the correct action isn’t strictly necessary (and natively iOS doesn’t change its appearance) but I think updating to the relevant return key type is a nice touch:

<TextInput  returnKeyType={ "next" }  .../><TextInput  returnKeyType={ "done" }  .../>

This indicates how to show a done return key on the final input, and a nextreturn key on the rest of em.

Putting it all together

The final gist is here:

You can see it in action on iOS and Android:

iOS
Android

--

--