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

PropPurpose
placeholderGrey hint text shown when the field is empty
secureTextEntrySet to true to mask input as dots — for password fields
keyboardTypeChanges which on-screen keyboard layout appears — 'email-address' shows @ and . prominently, 'numeric' shows a number pad, 'phone-pad' shows a phone-style keypad
autoCapitalizeControls automatic capitalization — 'none', 'sentences', 'words', or 'characters'
multilineSet 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.

Next up: With local component state and forms covered, the next page moves to sharing state across many components without prop-drilling, using the Context API.