Пошук уроків, статей та іншого контенту
Додайте перевірку обов’язкових полів, форматів і діапазонів та відображайте зрозумілі повідомлення про помилки.
Валідація форми перевіряє, чи відповідають введені дані правилам перед їх надсиланням. Найчастіше потрібно перевірити:
обов’язкові поля;
мінімальну та максимальну довжину;
формат значення, наприклад електронну адресу;
числовий діапазон;
правильність усього набору даних перед відправленням.
У React зручно зберігати значення полів у стані компонента, а помилки — в окремому об’єкті:
{
name: "Введіть ім’я",
email: "Введіть коректну електронну адресу"
}Ключами об’єкта є імена полів, а значеннями — повідомлення для користувача.
Є кілька поширених моментів для валідації:
Під час надсилання форми — обов’язковий етап.
Після втрати фокусу полем — користувач одразу отримує підказку.
Під час введення — помилка оновлюється після кожної зміни.
Зазвичай не варто показувати помилки для всіх полів одразу після відкриття форми. Краще показувати помилку:
після спроби надсилання;
або після того, як користувач залишив конкретне поле.
Для цього можна зберігати окремий стан touched, який показує, з якими полями користувач уже взаємодіяв.
Зручно винести правила в окрему функцію. Вона отримує всі значення форми та повертає об’єкт помилок.
function validateForm(values) {
const errors = {};
if (!values.name.trim()) {
errors.name = "Введіть ім’я";
} else if (values.name.trim().length < 2) {
errors.name = "Ім’я має містити щонайменше 2 символи";
}
if (!values.email.trim()) {
errors.email = "Введіть електронну адресу";
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(values.email)) {
errors.email = "Введіть коректну електронну адресу";
}
if (!values.age) {
errors.age = "Введіть вік";
} else if (!Number.isInteger(Number(values.age))) {
errors.age = "Вік має бути цілим числом";
} else if (Number(values.age) < 18 || Number(values.age) > 120) {
errors.age = "Вік має бути від 18 до 120 років";
}
if (!values.password) {
errors.password = "Введіть пароль";
} else if (values.password.length < 8) {
errors.password = "Пароль має містити щонайменше 8 символів";
}
return errors;
}Важливі деталі:
trim() ігнорує пробіли на початку та в кінці тексту;
значення з <input type="number"> у React все одно надходить як рядок;
тому перед числовими перевірками потрібно виконати Number(values.age);
перевірку цілого числа можна виконати за допомогою Number.isInteger().
Нижче наведено повний приклад React-компонента. Він перевіряє обов’язкові поля, формат електронної адреси, діапазон віку та мінімальну довжину пароля.
import { useState } from "react";
const initialValues = {
name: "",
email: "",
age: "",
password: "",
};
function validateForm(values) {
const errors = {};
if (!values.name.trim()) {
errors.name = "Введіть ім’я";
} else if (values.name.trim().length < 2) {
errors.name = "Ім’я має містити щонайменше 2 символи";
}
if (!values.email.trim()) {
errors.email = "Введіть електронну адресу";
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(values.email)) {
errors.email = "Введіть коректну електронну адресу";
}
if (!values.age) {
errors.age = "Введіть вік";
} else if (!Number.isInteger(Number(values.age))) {
errors.age = "Вік має бути цілим числом";
} else if (Number(values.age) < 18 || Number(values.age) > 120) {
errors.age = "Вік має бути від 18 до 120 років";
}
if (!values.password) {
errors.password = "Введіть пароль";
} else if (values.password.length < 8) {
errors.password = "Пароль має містити щонайменше 8 символів";
}
return errors;
}
function FieldError({ id, message }) {
if (!message) {
return null;
}
return (
<p id={id} role="alert" style={{ color: "crimson", margin: "4px 0 12px" }}>
{message}
</p>
);
}
export default function App() {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
const [submitted, setSubmitted] = useState(false);
const [successMessage, setSuccessMessage] = useState("");
function handleChange(event) {
const { name, value } = event.target;
const nextValues = {
...values,
[name]: value,
};
setValues(nextValues);
setSuccessMessage("");
if (touched[name] || submitted) {
setErrors(validateForm(nextValues));
}
}
function handleBlur(event) {
const { name } = event.target;
setTouched((currentTouched) => ({
...currentTouched,
[name]: true,
}));
setErrors(validateForm(values));
}
function handleSubmit(event) {
event.preventDefault();
const validationErrors = validateForm(values);
setErrors(validationErrors);
setSubmitted(true);
setTouched({
name: true,
email: true,
age: true,
password: true,
});
if (Object.keys(validationErrors).length > 0) {
setSuccessMessage("");
return;
}
setSuccessMessage("Форму успішно перевірено");
}
function shouldShowError(fieldName) {
return submitted || touched[fieldName];
}
return (
<main>
<h1>Реєстрація</h1>
<form onSubmit={handleSubmit} noValidate>
<div>
<label htmlFor="name">Ім’я</label>
<input
id="name"
name="name"
type="text"
value={values.name}
onChange={handleChange}
onBlur={handleBlur}
required
minLength={2}
aria-invalid={shouldShowError("name") && Boolean(errors.name)}
aria-describedby="name-error"
/>
{shouldShowError("name") && (
<FieldError id="name-error" message={errors.name} />
)}
</div>
<div>
<label htmlFor="email">Електронна адреса</label>
<input
id="email"
name="email"
type="email"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
required
aria-invalid={shouldShowError("email") && Boolean(errors.email)}
aria-describedby="email-error"
/>
{shouldShowError("email") && (
<FieldError id="email-error" message={errors.email} />
)}
</div>
<div>
<label htmlFor="age">Вік</label>
<input
id="age"
name="age"
type="number"
value={values.age}
onChange={handleChange}
onBlur={handleBlur}
required
min={18}
max={120}
aria-invalid={shouldShowError("age") && Boolean(errors.age)}
aria-describedby="age-error"
/>
{shouldShowError("age") && (
<FieldError id="age-error" message={errors.age} />
)}
</div>
<div>
<label htmlFor="password">Пароль</label>
<input
id="password"
name="password"
type="password"
value={values.password}
onChange={handleChange}
onBlur={handleBlur}
required
minLength={8}
aria-invalid={
shouldShowError("password") && Boolean(errors.password)
}
aria-describedby="password-error"
/>
{shouldShowError("password") && (
<FieldError id="password-error" message={errors.password} />
)}
</div>
<button type="submit">Зареєструватися</button>
{successMessage && (
<p role="status" style={{ color: "green" }}>
{successMessage}
</p>
)}
</form>
</main>
);
}Кожне поле отримує значення зі стану:
<input
name="email"
value={values.email}
onChange={handleChange}
/>React стає джерелом правди для форми. Значення змінюється лише через setValues.
Обробник handleChange використовує властивість name, тому один обробник працює для всіх полів:
function handleChange(event) {
const { name, value } = event.target;
setValues((currentValues) => ({
...currentValues,
[name]: value,
}));
}Повідомлення потрібно показувати поруч із відповідним полем:
{shouldShowError("email") && (
<FieldError id="email-error" message={errors.email} />
)}Якщо помилки немає, компонент FieldError повертає null і нічого не рендерить.
Атрибут aria-describedby пов’язує поле з текстом помилки:
<input
aria-describedby="email-error"
aria-invalid={Boolean(errors.email)}
/>Це покращує доступність форми для користувачів, які використовують допоміжні технології.
Обробник onBlur запускається, коли користувач залишає поле:
function handleBlur(event) {
const { name } = event.target;
setTouched((currentTouched) => ({
...currentTouched,
[name]: true,
}));
setErrors(validateForm(values));
}Після цього поле вважається опрацьованим, і його помилка може відображатися.
Обробник форми повинен скасувати стандартне перезавантаження сторінки:
function handleSubmit(event) {
event.preventDefault();
const validationErrors = validateForm(values);
setErrors(validationErrors);
if (Object.keys(validationErrors).length > 0) {
return;
}
// Надсилання коректних даних
}Якщо об’єкт помилок порожній, форма пройшла клієнтську перевірку.
У прикладі також використано стандартні атрибути HTML:
required — поле обов’язкове;
minLength — мінімальна довжина тексту;
min і max — числовий діапазон;
type="email" — поле очікує електронну адресу.
Атрибути HTML корисні як додатковий рівень перевірки та підказка браузеру. Власна функція validateForm потрібна для:
єдиного формату повідомлень;
контролю моменту показу помилки;
складніших правил;
однакової логіки валідації для різних сценаріїв.
У прикладі використано noValidate, щоб браузер не перехоплював надсилання форми власними повідомленнями. Це дає змогу повністю контролювати перевірку та текст помилок у React.
Повідомлення про помилки мають бути:
конкретними: Введіть електронну адресу, а не Некоректне значення;
зрозумілими без технічних термінів;
розташованими поруч із проблемним полем;
достатньо короткими;
орієнтованими на виправлення: Вік має бути від 18 до 120 років.
Не варто показувати користувачу повідомлення на кшталт ValidationError або Invalid input. Такі назви можуть бути зрозумілими розробнику, але не допомагають виправити форму.
Якщо помилки з’являються тільки після надсилання, користувач може не розуміти, яке саме поле потрібно виправити.
Краще поєднувати перевірку під час надсилання з перевіркою після blur.
Неініціалізована форма не повинна одразу виглядати як помилкова. Використовуйте touched або прапорець submitted, щоб контролювати момент показу повідомлень.
Значення поля введення надходить як рядок:
const age = "25";Перед числовою перевіркою перетворюйте його:
const numericAge = Number(age);Атрибути required, min і max не замінюють логіку React, якщо потрібні власні повідомлення або складніші правила.
Клієнтську валідацію можна обійти, тому дані потрібно повторно перевіряти на сервері перед збереженням або обробкою. Перевірка в React покращує взаємодію з формою, але не є захистом даних.
Значення контрольованої форми зберігаються в стані React.
Помилки зручно зберігати в окремому об’єкті, де ключ відповідає імені поля.
Функція validateForm централізує правила перевірки.
required, type, min, max і minLength доповнюють власну валідацію.
touched допомагає не показувати помилки до взаємодії з полем.
Під час надсилання потрібно перевіряти всю форму.
Повідомлення мають пояснювати проблему та підказувати спосіб її виправлення.