Tabbing Through Input Fields
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.
TextInput
s 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 next
return 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: