Урок 09

Работа с формами

Формы в React Native — это комбинация TextInput, Pressable и валидации.

Простая форма

import { useState } from 'react';
import { View, Text, TextInput, Pressable, StyleSheet } from 'react-native';

export default function LoginForm() {
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const [error, setError] = useState('');

    function handleSubmit() {
        if (!email.includes('@')) {
            setError('Некорректный email');
            return;
        }
        if (password.length < 8) {
            setError('Пароль минимум 8 символов');
            return;
        }
        setError('');
        console.log({ email, password });
    }

    return (
        <View style={styles.container}>
            <TextInput
                value={email}
                onChangeText={setEmail}
                placeholder="Email"
                keyboardType="email-address"
                autoCapitalize="none"
                style={styles.input}
            />
            <TextInput
                value={password}
                onChangeText={setPassword}
                placeholder="Пароль"
                secureTextEntry
                style={styles.input}
            />
            {error ? <Text style={styles.error}>{error}</Text> : null}
            <Pressable onPress={handleSubmit} style={styles.button}>
                <Text style={styles.buttonText}>Войти</Text>
            </Pressable>
        </View>
    );
}

Валидация

На лету или при отправке. На лету — через вычисляемые значения:

const emailError = email && !email.includes('@') ? 'Некорректный email' : '';
const passwordError = password && password.length < 8 ? 'Минимум 8 символов' : '';

Состояние формы

type FormState = {
    email: string;
    password: string;
    errors: Partial<Record<keyof Omit<FormState, 'errors'>, string>>;
};

const [form, setForm] = useState<FormState>({
    email: '',
    password: '',
    errors: {},
});

function update<K extends 'email' | 'password'>(key: K, value: string) {
    setForm((f) => ({ ...f, [key]: value, errors: { ...f.errors, [key]: undefined } }));
}

React Hook Form

Работает в React Native:

npm install react-hook-form
import { useForm, Controller } from 'react-hook-form';

type FormValues = {
    email: string;
    password: string;
};

export default function Form() {
    const { control, handleSubmit, formState: { errors } } = useForm<FormValues>({
        defaultValues: { email: '', password: '' },
    });

    function onSubmit(data: FormValues) {
        console.log(data);
    }

    return (
        <View>
            <Controller
                control={control}
                name="email"
                rules={{ required: 'Email обязателен' }}
                render={({ field }) => (
                    <TextInput
                        value={field.value}
                        onChangeText={field.onChange}
                        placeholder="Email"
                    />
                )}
            />
            {errors.email && <Text>{errors.email.message}</Text>}

            <Pressable onPress={handleSubmit(onSubmit)}>
                <Text>Войти</Text>
            </Pressable>
        </View>
    );
}

Controller нужен, потому что TextInput не поддерживает ref так же, как вебовый <input>.

Zod

import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

const schema = z.object({
    email: z.string().email('Некорректный email'),
    password: z.string().min(8, 'Минимум 8 символов'),
});

const { control, handleSubmit } = useForm({
    resolver: zodResolver(schema),
});

KeyboardAvoidingView

Клавиатура перекрывает форму — оберни в KeyboardAvoidingView:

import { KeyboardAvoidingView, Platform, ScrollView } from 'react-native';

<KeyboardAvoidingView
    behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
    style={{ flex: 1 }}
>
    <ScrollView keyboardShouldPersistTaps="handled">
        {/* форма */}
    </ScrollView>
</KeyboardAvoidingView>

Если ты уже знаешь React

В вебе — <form onSubmit={handleSubmit}>. В React Native нет form — отправка через нажатие на кнопку. Логика формы та же.

Итоги

  • Нет <form> — только Pressable
  • TextInput с onChangeText
  • React Hook Form работает
  • Controller для интеграции
  • Zod для схем валидации
  • KeyboardAvoidingView против клавиатуры