Forms are used in React Native applications to collect information from users. Login forms, registration forms, admission forms, profile forms, search forms, and payment forms are common examples.
TypeScript helps us define the structure of form data and makes it easier to work with input values, validation, events, and form submission.
TextInput commonly use
onChangeText, which receives the entered text as a string.
A form is a collection of input fields used to collect information from a user.
For example, a registration form may contain:
interface FormData {
name: string;
email: string;
phone: string;
}
This interface defines the structure of the form data.
const [form, setForm] =
useState<FormData>({
name: "",
email: "",
phone: ""
});
The state now follows the FormData interface.
<TextInput
value={form.name}
onChangeText={(text: string) =>
setForm({
...form,
name: text
})
}
/>
The text parameter received by onChangeText
is a string.
setForm(prev => ({
...prev,
name: "Rahul"
}));
The previous form values are preserved while the name is updated.
const updateField = (
field: keyof FormData,
value: string
): void => {
setForm(prev => ({
...prev,
[field]: value
}));
};
keyof FormData restricts the field parameter to valid
properties of the form.
<TextInput
value={form.name}
onChangeText={(text: string) =>
updateField("name", text)
}
/>
<TextInput
value={form.email}
onChangeText={(text: string) =>
updateField("email", text)
}
/>
<TextInput
value={form.email}
placeholder="Enter email"
keyboardType="email-address"
autoCapitalize="none"
onChangeText={(text: string) =>
updateField("email", text)
}
/>
The email is still stored as a string in the form state.
<TextInput
value={form.phone}
placeholder="Enter phone"
keyboardType="phone-pad"
onChangeText={(text: string) =>
updateField("phone", text)
}
/>
Phone numbers are commonly stored as strings because they may contain leading zeros or formatting characters.
interface LoginForm {
email: string;
password: string;
}
<TextInput
value={form.password}
secureTextEntry={true}
onChangeText={(text: string) =>
setForm({
...form,
password: text
})
}
/>
TextInput provides entered text as a string. If a form
field should contain a number, convert the value before storing it
as a number.
const [age, setAge] =
useState<number>(0);
onChangeText={(text: string) =>
setAge(Number(text))
}
Form validation checks whether the entered values are acceptable before submitting the form.
if (!form.name.trim()) {
setError("Name is required");
return;
}
if (!form.email.includes("@")) {
setError("Enter a valid email");
return;
}
This is a simple example of client-side validation.
const [error, setError] =
useState<string | null>(null);
The error state can contain an error message or null when there is no validation error.
{error && (
<Text>{error}</Text>
)}
Larger forms can store individual errors for different fields.
interface FormErrors {
name?: string;
email?: string;
phone?: string;
}
Each property is optional because not every field will necessarily contain an error.
const [errors, setErrors] =
useState<FormErrors>({});
This gives every form error a predictable structure.
const handleSubmit = (): void => {
if (!form.name.trim()) {
setError("Name is required");
return;
}
console.log(form);
};
The submit function can validate the form and then process the data.
const [loading, setLoading] =
useState<boolean>(false);
Loading state can prevent repeated submissions while an operation is in progress.
setLoading(true);
// submit data
setLoading(false);
<Button
title={loading ? "Saving..." : "Submit"}
disabled={loading}
onPress={handleSubmit}
/>
The button can be disabled while the form is being submitted.
const resetForm = (): void => {
setForm({
name: "",
email: "",
phone: ""
});
setError(null);
};
A reset function restores the form to its initial state.
interface InputProps {
value: string;
placeholder: string;
onChangeText: (text: string) => void;
}
function FormInput(
{
value,
placeholder,
onChangeText
}: InputProps
): React.JSX.Element {
return (
<TextInput
value={value}
placeholder={placeholder}
onChangeText={onChangeText}
/>
);
}
interface RegisterForm {
name: string;
email: string;
phone: string;
password: string;
}
A single interface can describe all fields required by a registration form.
any for form state.useState.string for TextInput values.Typed form data can be sent to an API after validation.
const submitForm = async (): Promise<void> => {
const response =
await fetch(
"https://example.com/api/register",
{
method: "POST",
headers: {
"Content-Type":
"application/json"
},
body: JSON.stringify(form)
}
);
};
interface RegisterForm {
name: string;
email: string;
password: string;
}
interface RegisterResponse {
success: boolean;
message: string;
userId?: number;
}
Separate interfaces can describe the data sent to the API and the data returned by the API.
import React, {
useState
} from "react";
import {
View,
Text,
TextInput,
Button,
StyleSheet
} from "react-native";
interface RegisterForm {
name: string;
email: string;
phone: string;
password: string;
}
export default function App(): React.JSX.Element {
const [form, setForm] =
useState<RegisterForm>({
name: "",
email: "",
phone: "",
password: ""
});
const [error, setError] =
useState<string | null>(null);
const [loading, setLoading] =
useState<boolean>(false);
const updateField = (
field: keyof RegisterForm,
value: string
): void => {
setForm(prev => ({
...prev,
[field]: value
}));
setError(null);
};
const handleSubmit = async (): Promise<void> => {
if (!form.name.trim()) {
setError(
"Name is required"
);
return;
}
if (!form.email.includes("@")) {
setError(
"Enter a valid email"
);
return;
}
if (!form.password) {
setError(
"Password is required"
);
return;
}
try {
setLoading(true);
console.log(form);
} finally {
setLoading(false);
}
};
const resetForm = (): void => {
setForm({
name: "",
email: "",
phone: "",
password: ""
});
setError(null);
};
return (
<View style={styles.container}>
<Text style={styles.title}>
Registration Form
</Text>
<TextInput
style={styles.input}
placeholder="Name"
value={form.name}
onChangeText={(text: string) =>
updateField("name", text)
}
/>
<TextInput
style={styles.input}
placeholder="Email"
value={form.email}
keyboardType="email-address"
autoCapitalize="none"
onChangeText={(text: string) =>
updateField("email", text)
}
/>
<TextInput
style={styles.input}
placeholder="Phone"
value={form.phone}
keyboardType="phone-pad"
onChangeText={(text: string) =>
updateField("phone", text)
}
/>
<TextInput
style={styles.input}
placeholder="Password"
value={form.password}
secureTextEntry={true}
onChangeText={(text: string) =>
updateField("password", text)
}
/>
{error && (
<Text style={styles.error}>
{error}
</Text>
)}
<Button
title={
loading
? "Submitting..."
: "Submit"
}
disabled={loading}
onPress={handleSubmit}
/>
<View style={styles.space} />
<Button
title="Reset"
onPress={resetForm}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20
},
title: {
fontSize: 24,
fontWeight: "bold",
marginBottom: 20
},
input: {
borderWidth: 1,
borderRadius: 8,
padding: 12,
marginBottom: 12
},
error: {
marginBottom: 12
},
space: {
height: 10
}
});
TypeScript makes React Native forms easier to organize by defining the structure of form data. Interfaces can be used with form state, TextInput, validation, error handling, submission functions, reusable inputs, and API requests.
You should now understand typed form state, TextInput values, generic field handlers, validation, error states, loading states, reset functions, reusable inputs, API submission, and complete typed forms.
useState for form values.TextInput provides entered text as a string.keyof for reusable field update functions.
Question:
What type does React Native TextInput normally provide through
onChangeText?