Урок 05

Валидация формы

Валидация проверяет данные до отправки на сервер. Бывает на лету (при вводе) и при отправке.

Валидация при отправке

function Form() {
    const [email, setEmail] = useState('');
    const [error, setError] = useState('');

    function handleSubmit(e: React.FormEvent) {
        e.preventDefault();

        if (!email.includes('@')) {
            setError('Введите корректный email');
            return;
        }

        setError('');
        // отправка
    }

    return (
        <form onSubmit={handleSubmit}>
            <input value={email} onChange={(e) => setEmail(e.target.value)} />
            {error && <p className="error">{error}</p>}
            <button>Отправить</button>
        </form>
    );
}

Валидация на лету

function Form() {
    const [email, setEmail] = useState('');

    const isValid = email === '' || email.includes('@');

    return (
        <input
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            className={isValid ? '' : 'invalid'}
        />
    );
}

Пустая строка считается валидной — ошибка появляется только когда пользователь начал вводить.

Валидация при потере фокуса

const [email, setEmail] = useState('');
const [touched, setTouched] = useState(false);

const isValid = email.includes('@');

<input
    value={email}
    onChange={(e) => setEmail(e.target.value)}
    onBlur={() => setTouched(true)}
/>

{touched && !isValid && <p className="error">Некорректный email</p>}

touched показывает, что пользователь уже взаимодействовал с полем.

Несколько полей

const [values, setValues] = useState({ email: '', password: '' });
const [errors, setErrors] = useState<Record<string, string>>({});

function validate() {
    const newErrors: Record<string, string> = {};

    if (!values.email.includes('@')) {
        newErrors.email = 'Некорректный email';
    }

    if (values.password.length < 8) {
        newErrors.password = 'Пароль должен быть минимум 8 символов';
    }

    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
}

function handleSubmit(e: React.FormEvent) {
    e.preventDefault();

    if (!validate()) return;

    // отправка
}

Отображение ошибок

<input
    name="email"
    value={values.email}
    onChange={handleChange}
    onBlur={validate}
/>
{errors.email && <p className="error">{errors.email}</p>}

Встроенная HTML-валидация

Браузер умеет валидировать сам:

<form>
    <input type="email" required />
    <input type="text" minLength={3} maxLength={20} />
    <input type="number" min={0} max={100} />
    <input type="text" pattern="[A-Za-z]+" />
</form>

Атрибуты: required, min, max, minLength, maxLength, pattern, type.

Для кастомного сообщения:

<input
    required
    onInvalid={(e) => (e.target as HTMLInputElement).setCustomValidity('Введи email')}
    onInput={(e) => (e.target as HTMLInputElement).setCustomValidity('')}
/>

Преимущества JS-валидации

  • Сообщения на русском
  • Полный контроль над UX
  • Асинхронные проверки (занят ли email)
  • Валидация на сервере дублируется

Серверная валидация

const [serverError, setServerError] = useState('');

async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();

    const res = await fetch('/api/register', {
        method: 'POST',
        body: JSON.stringify(values),
    });

    if (!res.ok) {
        const data = await res.json();
        setServerError(data.message);
        return;
    }
}

Серверная валидация обязательна — клиентскую можно обойти.

Итоги

  • Валидация при отправке и на лету
  • touched показывает взаимодействие
  • Объект errors для нескольких полей
  • HTML-валидация даёт базу
  • JS-валидация — контроль и локализация
  • Серверная валидация — обязательна