Forms & TextInput
HTML has a dedicated <form> element, built-in form submission, and a
collection of specialized input types. React Native has none of that. There is exactly
one component for typed text input — TextInput — and no concept
of a "form" wrapping multiple fields together. Every bit of input state, validation, and
submission logic is something you build yourself, typically using the
useState hook covered on the State & Hooks
page.
The Controlled-Input Pattern
Just like controlled inputs in ordinary React, a TextInput is "controlled" by
tying its value prop to a piece of state, and updating that state through the
onChangeText callback every time the user types:
import React, { useState } from 'react';
import { View, TextInput, Text, StyleSheet } from 'react-native';
export default function NameInput() {
const [name, setName] = useState('');
return (
<View style={styles.container}>
<TextInput
style={styles.input}
placeholder="Enter your name"
value={name}
onChangeText={setName}
/>
<Text>Hello, {name || 'stranger'}!</Text>
</View>
);
}
const styles = StyleSheet.create({
container: { padding: 16 },
input: {
borderWidth: 1,
borderColor: '#cccccc',
borderRadius: 6,
padding: 10,
marginBottom: 8,
},
});
onChangeText passes the new text value directly (not an event object with a
target.value, unlike the web's onChange) — which is why
setName can be passed straight in as the handler.
Common TextInput Props Reference
| Prop | Purpose |
|---|---|
placeholder | Grey hint text shown when the field is empty |
secureTextEntry | Set to true to mask input as dots — for password fields |
keyboardType | Changes which on-screen keyboard layout appears — 'email-address' shows @ and . prominently, 'numeric' shows a number pad, 'phone-pad' shows a phone-style keypad |
autoCapitalize | Controls automatic capitalization — 'none', 'sentences', 'words', or 'characters' |
multiline | Set to true to allow multiple lines of text, like a text area |
<TextInput
placeholder="Email address"
keyboardType="email-address"
autoCapitalize="none"
value={email}
onChangeText={setEmail}
/>
<TextInput
placeholder="Password"
secureTextEntry
value={password}
onChangeText={setPassword}
/>
Manual Validation
Since there's no built-in form/validator system, validation is ordinary JavaScript logic you run yourself — typically when a submit button is pressed. Here's a full worked example: an email/password form that validates on submit and conditionally displays an error message:
import React, { useState } from 'react';
import { View, TextInput, Text, Pressable, StyleSheet } from 'react-native';
export default function SignupForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
function handleSubmit() {
if (!email.includes('@')) {
setError('Please enter a valid email address.');
return;
}
if (password.length < 6) {
setError('Password must be at least 6 characters.');
return;
}
setError('');
// Validation passed — proceed with sign-up (e.g. call an API)
}
return (
<View style={styles.container}>
<TextInput
style={styles.input}
placeholder="Email address"
keyboardType="email-address"
autoCapitalize="none"
value={email}
onChangeText={setEmail}
/>
<TextInput
style={styles.input}
placeholder="Password"
secureTextEntry
value={password}
onChangeText={setPassword}
/>
{error ? <Text style={styles.error}>{error}</Text> : null}
<Pressable style={styles.button} onPress={handleSubmit}>
<Text style={styles.buttonText}>Sign Up</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
container: { padding: 16 },
input: { borderWidth: 1, borderColor: '#cccccc', borderRadius: 6, padding: 10, marginBottom: 10 },
error: { color: '#d32f2f', marginBottom: 10 },
button: { backgroundColor: '#2196f3', padding: 12, borderRadius: 8 },
buttonText: { color: '#ffffff', textAlign: 'center', fontWeight: 'bold' },
});
There's no framework validating anything for you — handleSubmit is plain JavaScript
checking plain state values, and setting error just re-renders the component with
(or without) the error text shown. This hands-on approach is typical of React Native: you get
full control, in exchange for writing the logic yourself (or reaching for a validation library
in larger apps).
Keyboard-Avoidance: Fixing a Covered Input Field
A very real, very commonly hit problem: the on-screen keyboard slides up from the bottom of the
screen and can cover the very input field the user is typing into, especially in a form near the
bottom of the layout. The standard fix is wrapping your form in
KeyboardAvoidingView, which automatically resizes or repositions its content so the
focused input stays visible above the keyboard:
import React from 'react';
import { KeyboardAvoidingView, TextInput, Platform, StyleSheet } from 'react-native';
export default function LoginForm() {
return (
<KeyboardAvoidingView
style={styles.container}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
>
<TextInput style={styles.input} placeholder="Email" />
<TextInput style={styles.input} placeholder="Password" secureTextEntry />
</KeyboardAvoidingView>
);
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', padding: 16 },
input: { borderWidth: 1, borderColor: '#cccccc', borderRadius: 6, padding: 10, marginBottom: 10 },
});
The behavior prop is the detail to remember: iOS and Android handle keyboard
avoidance differently at the native level, so React Native asks you to pick
'padding' on iOS (adds bottom padding to push content up) versus
'height' on Android (shrinks the container's height instead) — checked at runtime
with Platform.OS.
Dismissing the Keyboard on Outside Tap
Another small but common polish detail: letting the user tap anywhere outside an input to
dismiss the keyboard, rather than forcing them to find and press a "Done" button. Wrap the
screen's content in TouchableWithoutFeedback and call Keyboard.dismiss()
on press:
import React from 'react';
import { TouchableWithoutFeedback, Keyboard, View, TextInput } from 'react-native';
export default function Screen() {
return (
<TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}>
<View style={{ flex: 1 }}>
<TextInput placeholder="Tap outside me to dismiss the keyboard" />
</View>
</TouchableWithoutFeedback>
);
}
TouchableWithoutFeedback is a version of the touchable components from the
Touches & Gestures page that gives no visual
feedback of its own — it exists purely to capture the tap and run a handler, which here simply
asks the system to dismiss whichever input currently has keyboard focus.