React Native Forms: Inputs, Validation and the Keyboard

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.

Pieces of a React Native form
JobTool
Hold the valuesuseState with one object, or one hook per field
Check the valuesA pure validate(values) function returning an errors object
Show errorsText under each field, linked by accessibilityLabel or a live region
Keep fields visibleKeyboardAvoidingView with behavior="padding" on iOS
Tap submit with the keyboard openkeyboardShouldPersistTaps="handled" on the ScrollView
Move between fieldsreturnKeyType="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.

Validation preview

The checks run the validate function from the code below. Messages appear after you leave a field or press the button.

Form code to copy

The validate function

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;
}

Signup screen with touched fields and keyboard handling

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>
  );
}

When to show an error

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.

Making errors accessible

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.

How it works

  1. Each field writes into one values object through onChangeText.
  2. onBlur marks a field as touched, so its error can show without shouting at someone mid-typing.
  3. validate(values) runs on every render and returns an object with a message per broken rule.
  4. Submit marks every field as touched and stops if the errors object has any keys.
  5. With no errors left, the handler sends the values and can call Keyboard.dismiss().

Tips

Frequently asked questions

Do I need a form library in React Native?

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.

How do I stop the keyboard covering my inputs?

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.

Why does my submit button need two taps?

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.

How do I close the keyboard after submitting?

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.

More in Guides

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.

Product

Who It's For

Features

Resources

Company

View Sitemap