A dependable React Native form needs three pieces: TextInputs controlled by state, one validate function that returns an error per field, and keyboard handling. Show each message once the person has left that field or pressed submit. Wrap the screen in KeyboardAvoidingView and set keyboardShouldPersistTaps="handled" on the ScrollView so the submit button stays reachable.
| Job | Tool |
|---|---|
| Hold the values | useState with one object, or one hook per field |
| Check the values | A pure validate(values) function returning an errors object |
| Show errors | Text under each field, linked by accessibilityLabel or a live region |
| Keep fields visible | KeyboardAvoidingView with behavior="padding" on iOS |
| Tap submit with the keyboard open | keyboardShouldPersistTaps="handled" on the ScrollView |
| Move between fields | returnKeyType="next" and focus() on the next ref |
Updated
The validation rules on this page are plain JavaScript, so the preview below runs the exact function you would paste into your React Native screen. Fill the fields, leave them, and press Create account to watch each message appear and clear.
The checks run the validate function from the code below. Messages appear after you leave a field or press the button.
Plain JavaScript with no React Native imports, so it runs in tests, on the web preview and on a server.
export function validate(v) {
const e = {};
if (!v.name.trim()) e.name = "Enter your name.";
const email = v.email.trim();
if (!email) e.email = "Enter your email address.";
else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) e.email = "That email address is missing an @ or a domain.";
if (v.password.length < 8) e.password = "Use at least 8 characters.";
if (v.confirm !== v.password) e.confirm = "The two passwords do not match.";
return e;
}One values object, one touched object, and a Field helper that shows its error only when it should.
import { useState } from "react";
import {
Keyboard, KeyboardAvoidingView, Platform, Pressable, ScrollView, Text, TextInput, View,
} from "react-native";
import { validate } from "./validate";
const EMPTY = { name: "", email: "", password: "", confirm: "" };
export function SignupScreen({ onSubmit }) {
const [values, setValues] = useState(EMPTY);
const [touched, setTouched] = useState({});
const errors = validate(values);
const set = (key) => (text) => setValues((v) => ({ ...v, [key]: text }));
const blur = (key) => () => setTouched((t) => ({ ...t, [key]: true }));
const submit = () => {
setTouched({ name: true, email: true, password: true, confirm: true });
if (Object.keys(errors).length) return;
Keyboard.dismiss();
onSubmit(values);
};
const Field = ({ id, label, ...rest }) => {
const error = touched[id] ? errors[id] : undefined;
return (
<View style={{ marginBottom: 16 }}>
<Text>{label}</Text>
<TextInput
value={values[id]}
onChangeText={set(id)}
onBlur={blur(id)}
accessibilityLabel={error ? `${label}, ${error}` : label}
style={{ borderWidth: 1, borderColor: error ? "#dc2626" : "#cbd5e1", borderRadius: 8, padding: 10 }}
{...rest}
/>
{error ? <Text accessibilityLiveRegion="polite" style={{ color: "#dc2626" }}>{error}</Text> : null}
</View>
);
};
return (
<KeyboardAvoidingView style={{ flex: 1 }} behavior={Platform.OS === "ios" ? "padding" : undefined}>
<ScrollView keyboardShouldPersistTaps="handled" contentContainerStyle={{ padding: 20 }}>
{Field({ id: "name", label: "Name", autoComplete: "name" })}
{Field({ id: "email", label: "Email", keyboardType: "email-address", autoCapitalize: "none", autoComplete: "email" })}
{Field({ id: "password", label: "Password", secureTextEntry: true, autoComplete: "new-password" })}
{Field({ id: "confirm", label: "Confirm password", secureTextEntry: true, autoComplete: "new-password" })}
<Pressable accessibilityRole="button" onPress={submit} style={{ backgroundColor: "#2563eb", padding: 14, borderRadius: 8 }}>
<Text style={{ color: "#fff", textAlign: "center" }}>Create account</Text>
</Pressable>
</ScrollView>
</KeyboardAvoidingView>
);
}
Showing every rule in red the moment the screen opens feels like a scolding. Track which fields someone has touched and only reveal a message after they leave that field, or after a submit attempt.
Once a message is showing, re-run the check on every keystroke so it disappears the moment the value becomes valid. That instant confirmation is the payoff for waiting before showing it.
A red border alone means nothing to someone using a screen reader or with low colour vision. Write the problem as text, and include it in the field's accessibilityLabel so it is read when focus lands there.
On Android, accessibilityLiveRegion="polite" on the error Text makes TalkBack announce the message when it appears without moving focus.
No. A few fields work well with useState and a validate function like the one on this page. Libraries help when a form has many fields, nested arrays or async checks, and React Hook Form describes itself as working on the web and in React Native.
Wrap the form in KeyboardAvoidingView. It changes its height, position or bottom padding to match the keyboard; behavior="padding" is the common choice on iOS. keyboardVerticalOffset accounts for a header above the view and defaults to 0.
The ScrollView's keyboardShouldPersistTaps defaults to "never", so the first tap only dismisses the keyboard. Set it to "handled" and the button receives the tap straight away.
Call Keyboard.dismiss(), which the docs describe as dismissing the active keyboard and removing focus.
Related: React Native TextInput props, React Native password input, React Hook Form examples, React Native dropdown, Regex tester.
Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.