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

Валидация при отправке формы строится вокруг перехвата события submit, в котором данные формы проходят полный цикл проверки до передачи на сервер. Основная задача — предотвратить некорректные данные ещё на клиентском уровне и сформировать структурированный набор ошибок.

const form = document.querySelector('form');

form.addEventListener('submit', (event) => {
    event.preventDefault();

    const data = {
        email: form.email.value,
        password: form.password.value,
        phone: form.phone.value
    };

    const errors = validateForm(data);

    if (Object.keys(errors).length > 0) {
        renderErrors(errors);
        return;
    }

    form.submit();
});

В этом подходе отправка формы блокируется до завершения всех проверок, что позволяет контролировать поток данных и исключать некорректные запросы.


Подготовка и нормализация данных

Перед валидацией данные приводятся к единому виду. Это снижает количество ложных ошибок и упрощает правила проверки.

const normalizeData = (data) => {
    return {
        email: data.email.trim().toLowerCase(),
        password: data.password,
        phone: data.phone.replace(/\s+/g, '')
    };
};

Часто используется:

  • удаление пробелов (trim)
  • приведение email к нижнему регистру
  • очистка телефонных номеров от разделителей
  • замена null и undefined на пустые строки

Проверка обязательных полей

Первичный этап валидации связан с контролем заполненности полей. Для этого используется validator.js:

import validator from 'validator';

if (validator.isEmpty(data.email)) {
    errors.email = 'Email обязателен';
}

if (validator.isEmpty(data.password)) {
    errors.password = 'Пароль обязателен';
}

Функция isEmpty проверяет не только пустую строку, но и строки, содержащие только пробелы (при соответствующей предварительной нормализации).


Типовые проверки Validator.js

Библиотека предоставляет набор универсальных функций, применяемых при проверке формы.

Проверка email

if (!validator.isEmail(data.email)) {
    errors.email = 'Некорректный email';
}

Проверка длины строки

if (!validator.isLength(data.password, { min: 8 })) {
    errors.password = 'Пароль должен содержать минимум 8 символов';
}

Проверка числовых значений

if (!validator.isInt(data.age, { min: 18 })) {
    errors.age = 'Возраст должен быть не менее 18';
}

Проверка телефона

if (!validator.isMobilePhone(data.phone, 'any')) {
    errors.phone = 'Некорректный номер телефона';
}

Проверка URL

if (!validator.isURL(data.website)) {
    errors.website = 'Некорректный URL';
}

Составные правила валидации

Реальные формы требуют комбинированных проверок, где одно поле проходит несколько условий одновременно.

if (
    !validator.isLength(data.password, { min: 8 }) ||
    !/[A-Z]/.test(data.password) ||
    !/[0-9]/.test(data.password)
) {
    errors.password = 'Пароль не соответствует требованиям сложности';
}

Также часто применяется проверка совпадения значений:

if (!validator.equals(data.password, data.confirmPassword)) {
    errors.confirmPassword = 'Пароли не совпадают';
}

Формирование структуры ошибок

Для удобной обработки ошибок на интерфейсе используется объект с привязкой к полям формы.

const validateForm = (data) => {
    const errors = {};

    if (validator.isEmpty(data.email)) {
        errors.email = 'Email обязателен';
    } else if (!validator.isEmail(data.email)) {
        errors.email = 'Некорректный email';
    }

    if (!validator.isLength(data.password, { min: 8 })) {
        errors.password = 'Пароль слишком короткий';
    }

    return errors;
};

Такая структура позволяет напрямую связывать ошибки с элементами DOM.


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

Ошибки обычно отображаются рядом с соответствующими полями.

const renderErrors = (errors) => {
    Object.keys(errors).forEach((field) => {
        const errorElement = document.querySelector(`#${field}-error`);
        if (errorElement) {
            errorElement.textContent = errors[field];
        }
    });
};

Перед повторной валидацией старые сообщения очищаются:

const clearErrors = () => {
    document.querySelectorAll('.error').forEach((el) => {
        el.textContent = '';
    });
};

Прерывание отправки формы

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

form.addEventListener('submit', (event) => {
    event.preventDefault();

    clearErrors();

    const normalized = normalizeData({
        email: form.email.value,
        password: form.password.value
    });

    const errors = validateForm(normalized);

    if (Object.keys(errors).length > 0) {
        renderErrors(errors);
        return;
    }

    form.submit();
});

Санитизация данных Validator.js

Помимо проверки, библиотека используется для подготовки данных к безопасному использованию.

const safeData = {
    email: validator.normalizeEmail(data.email),
    comment: validator.escape(data.comment)
};

Основные функции:

  • normalizeEmail — приводит email к каноническому виду
  • escape — экранирует HTML-символы
  • trim — удаляет лишние пробелы

Условная логика сложных форм

В формах с зависимыми полями применяется динамическая валидация.

if (data.hasCompany === true) {
    if (validator.isEmpty(data.companyName)) {
        errors.companyName = 'Укажите название компании';
    }
}

Такие условия позволяют адаптировать проверку под контекст заполнения.


Асинхронная проверка данных

Некоторые проверки требуют обращения к серверу, например проверка уникальности email.

const checkEmailExists = async (email) => {
    const response = await fetch(`/api/check-email?email=${email}`);
    const result = await response.json();
    return result.exists;
};

Интеграция в общий поток:

if (await checkEmailExists(data.email)) {
    errors.email = 'Email уже используется';
}

Композиция правил валидации

При увеличении сложности формы правила выносятся в отдельные функции.

const rules = {
    email: (value) => {
        if (validator.isEmpty(value)) return 'Email обязателен';
        if (!validator.isEmail(value)) return 'Некорректный email';
        return null;
    },

    password: (value) => {
        if (!validator.isLength(value, { min: 8 })) {
            return 'Минимум 8 символов';
        }
        return null;
    }
};

Применение:

Object.keys(rules).forEach((field) => {
    const error = rules[field](data[field]);
    if (error) errors[field] = error;
});

Обработка множественных ошибок одного поля

Иногда требуется возвращать сразу несколько сообщений для одного поля.

const passwordErrors = [];

if (!validator.isLength(data.password, { min: 8 })) {
    passwordErrors.push('Минимум 8 символов');
}

if (!/[A-Z]/.test(data.password)) {
    passwordErrors.push('Должна быть заглавная буква');
}

if (passwordErrors.length > 0) {
    errors.password = passwordErrors;
}

Интеграция с UI-состоянием формы

Валидация часто связана с динамическим состоянием интерфейса: блокировка кнопки, подсветка полей, индикация загрузки.

const submitButton = form.querySelector('button');

if (Object.keys(errors).length > 0) {
    submitButton.disabled = true;
} else {
    submitButton.disabled = false;
}

Защита от повторной отправки

При асинхронной проверке важно предотвращать повторный submit.

let isSubmitting = false;

form.addEventListener('submit', async (event) => {
    event.preventDefault();

    if (isSubmitting) return;
    isSubmitting = true;

    const errors = await validateFormAsync(data);

    isSubmitting = false;

    if (Object.keys(errors).length > 0) {
        renderErrors(errors);
        return;
    }

    form.submit();
});