Lesson 74 of 60 – TypeScript with Forms
100%

TypeScript with Forms

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.

Note: In React Native, form inputs such as TextInput commonly use onChangeText, which receives the entered text as a string.

1. What is a Form?

A form is a collection of input fields used to collect information from a user.

For example, a registration form may contain:

  • Name
  • Email
  • Mobile number
  • Password

2. Why Use TypeScript with Forms?

  • Defines the structure of form data.
  • Helps prevent incorrect value types.
  • Improves editor suggestions.
  • Makes validation easier to organize.
  • Improves reusable form components.
  • Makes large forms easier to maintain.

3. Creating a Form Interface

interface FormData {
    name: string;
    email: string;
    phone: string;
}

This interface defines the structure of the form data.

4. Creating Form State

const [form, setForm] =
    useState<FormData>({
        name: "",
        email: "",
        phone: ""
    });

The state now follows the FormData interface.

5. TextInput with TypeScript

<TextInput
    value={form.name}
    onChangeText={(text: string) =>
        setForm({
            ...form,
            name: text
        })
    }
/>

The text parameter received by onChangeText is a string.

6. Updating Form State

setForm(prev => ({
    ...prev,
    name: "Rahul"
}));

The previous form values are preserved while the name is updated.

7. Creating a Generic Input Handler

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.

8. Using the Generic Handler

<TextInput
    value={form.name}
    onChangeText={(text: string) =>
        updateField("name", text)
    }
/>

<TextInput
    value={form.email}
    onChangeText={(text: string) =>
        updateField("email", text)
    }
/>

9. Email Input

<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.

10. Phone Input

<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.

11. Password Input

interface LoginForm {
    email: string;
    password: string;
}

<TextInput
    value={form.password}
    secureTextEntry={true}
    onChangeText={(text: string) =>
        setForm({
            ...form,
            password: text
        })
    }
/>

12. Number Values in Forms

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

13. Form Validation

Form validation checks whether the entered values are acceptable before submitting the form.

if (!form.name.trim()) {
    setError("Name is required");
    return;
}

14. Email Validation

if (!form.email.includes("@")) {

    setError("Enter a valid email");

    return;
}

This is a simple example of client-side validation.

15. Error State

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

16. Multiple Validation Errors

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.

17. Error State with an Interface

const [errors, setErrors] =
    useState<FormErrors>({});

This gives every form error a predictable structure.

18. Submit Function

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.

19. Loading State During Submission

const [loading, setLoading] =
    useState<boolean>(false);

Loading state can prevent repeated submissions while an operation is in progress.

setLoading(true);

// submit data

setLoading(false);

20. Submit Button

<Button
    title={loading ? "Saving..." : "Submit"}
    disabled={loading}
    onPress={handleSubmit}
/>

The button can be disabled while the form is being submitted.

21. Resetting a Form

const resetForm = (): void => {

    setForm({
        name: "",
        email: "",
        phone: ""
    });

    setError(null);
};

A reset function restores the form to its initial state.

22. Reusable Form Input Component

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

23. Form Interface for Registration

interface RegisterForm {
    name: string;
    email: string;
    phone: string;
    password: string;
}

A single interface can describe all fields required by a registration form.

24. Common TypeScript Form Mistakes

  • Using any for form state.
  • Forgetting to type form data.
  • Assuming TextInput returns a number.
  • Using incorrect property names.
  • Not handling nullable error states.
  • Updating form state without preserving other fields.
  • Submitting invalid data without validation.

25. Best Practices for Typed Forms

  • Create an interface for form data.
  • Use typed state with useState.
  • Keep input values controlled.
  • Use string for TextInput values.
  • Convert numeric input when a number is required.
  • Keep validation logic organized.
  • Use reusable typed input components.
  • Clear errors when appropriate.

26. Form Data with API Submission

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

};

27. Form Data and API Response Types

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.

28. Complete Typed Form Example

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
    }

});

29. Benefits of Typed Forms

  • Form structure is clearly defined.
  • Incorrect field names can be detected during development.
  • Input handlers become easier to reuse.
  • Validation logic becomes easier to organize.
  • API submission becomes more predictable.
  • Reusable form components become safer.
  • Large forms become easier to maintain.

30. Summary

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.

📌 Key Points

  • Create an interface for form data.
  • Use typed useState for form values.
  • TextInput provides entered text as a string.
  • Use keyof for reusable field update functions.
  • Validate form data before submission.
  • Use typed error and loading states.
  • Use reusable typed input components.
  • Use separate interfaces for form data and API responses when appropriate.

🧠 Quick Quiz

Question: What type does React Native TextInput normally provide through onChangeText?