← Назад к вопросам
Создать форму регистрации с валидацией на React
1.7 Middle🔥 191 комментариев
#React#State Management
Условие
Создайте React-компонент формы регистрации с валидацией полей в реальном времени.
Требования
-
Поля формы:
- Email (валидация формата)
- Пароль (минимум 8 символов, буквы и цифры)
- Подтверждение пароля (должно совпадать)
- Имя пользователя (от 3 до 20 символов)
-
Валидация:
- Показывать ошибки в реальном времени при вводе
- Блокировать кнопку отправки при наличии ошибок
- Показывать зелёную галочку для валидных полей
-
Использовать функциональные компоненты и хуки
Бонус
Добавьте индикатор силы пароля (слабый/средний/сильный).
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
Задача на валидирующую форму регистрации — классическая, показывает понимание управления состоянием и валидации в React. Создадим полное решение.
Решение 1: Базовая форма с валидацией
import React, { useState, useMemo } from "react";
interface FormErrors {
email?: string;
password?: string;
confirmPassword?: string;
username?: string;
}
interface FormData {
email: string;
password: string;
confirmPassword: string;
username: string;
}
function RegistrationForm() {
const [formData, setFormData] = useState<FormData>({
email: "",
password: "",
confirmPassword: "",
username: "",
});
// Валидация
const errors = useMemo<FormErrors>(() => {
const newErrors: FormErrors = {};
// Валидация email
if (formData.email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(formData.email)) {
newErrors.email = "Некорректный формат email";
}
} else {
newErrors.email = "Email обязателен";
}
// Валидация пароля
if (formData.password) {
if (formData.password.length < 8) {
newErrors.password = "Пароль должен быть минимум 8 символов";
} else if (!/[a-zA-Z]/.test(formData.password)) {
newErrors.password = "Пароль должен содержать буквы";
} else if (!/\d/.test(formData.password)) {
newErrors.password = "Пароль должен содержать цифры";
}
} else {
newErrors.password = "Пароль обязателен";
}
// Валидация подтверждения пароля
if (formData.confirmPassword) {
if (formData.confirmPassword !== formData.password) {
newErrors.confirmPassword = "Пароли не совпадают";
}
} else {
newErrors.confirmPassword = "Подтверждение пароля обязательно";
}
// Валидация имени пользователя
if (formData.username) {
if (formData.username.length < 3) {
newErrors.username = "Имя должно быть минимум 3 символа";
} else if (formData.username.length > 20) {
newErrors.username = "Имя должно быть максимум 20 символов";
} else if (!/^[a-zA-Z0-9_]+$/.test(formData.username)) {
newErrors.username = "Имя может содержать только буквы, цифры и подчёркивание";
}
} else {
newErrors.username = "Имя обязательно";
}
return newErrors;
}, [formData]);
const isFormValid = Object.keys(errors).length === 0 &&
Object.values(formData).every(val => val.length > 0);
const handleChange = (
e: React.ChangeEvent<HTMLInputElement>
) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (isFormValid) {
console.log("Форма отправлена:", formData);
// Отправка на сервер
}
};
return (
<form onSubmit={handleSubmit} className="w-full max-w-md mx-auto p-6">
<h1 className="text-2xl font-bold mb-6">Регистрация</h1>
{/* Email */}
<div className="mb-4">
<label htmlFor="email" className="block font-medium mb-1">
Email
</label>
<div className="relative">
<input
id="email"
name="email"
type="email"
value={formData.email}
onChange={handleChange}
className={`w-full px-3 py-2 border rounded ${
errors.email ? "border-red-500" : "border-gray-300"
}`}
/>
{!errors.email && formData.email && (
<span className="absolute right-3 top-2 text-green-500">✓</span>
)}
</div>
{errors.email && (
<p className="text-red-500 text-sm mt-1">{errors.email}</p>
)}
</div>
{/* Username */}
<div className="mb-4">
<label htmlFor="username" className="block font-medium mb-1">
Имя пользователя
</label>
<div className="relative">
<input
id="username"
name="username"
type="text"
value={formData.username}
onChange={handleChange}
className={`w-full px-3 py-2 border rounded ${
errors.username ? "border-red-500" : "border-gray-300"
}`}
/>
{!errors.username && formData.username && (
<span className="absolute right-3 top-2 text-green-500">✓</span>
)}
</div>
{errors.username && (
<p className="text-red-500 text-sm mt-1">{errors.username}</p>
)}
</div>
{/* Password */}
<div className="mb-4">
<label htmlFor="password" className="block font-medium mb-1">
Пароль
</label>
<div className="relative">
<input
id="password"
name="password"
type="password"
value={formData.password}
onChange={handleChange}
className={`w-full px-3 py-2 border rounded ${
errors.password ? "border-red-500" : "border-gray-300"
}`}
/>
{!errors.password && formData.password && (
<span className="absolute right-3 top-2 text-green-500">✓</span>
)}
</div>
{errors.password && (
<p className="text-red-500 text-sm mt-1">{errors.password}</p>
)}
</div>
{/* Confirm Password */}
<div className="mb-6">
<label htmlFor="confirmPassword" className="block font-medium mb-1">
Подтверждение пароля
</label>
<div className="relative">
<input
id="confirmPassword"
name="confirmPassword"
type="password"
value={formData.confirmPassword}
onChange={handleChange}
className={`w-full px-3 py-2 border rounded ${
errors.confirmPassword ? "border-red-500" : "border-gray-300"
}`}
/>
{!errors.confirmPassword && formData.confirmPassword && (
<span className="absolute right-3 top-2 text-green-500">✓</span>
)}
</div>
{errors.confirmPassword && (
<p className="text-red-500 text-sm mt-1">{errors.confirmPassword}</p>
)}
</div>
<button
type="submit"
disabled={!isFormValid}
className={`w-full py-2 rounded font-medium ${
isFormValid
? "bg-blue-500 text-white hover:bg-blue-600"
: "bg-gray-300 text-gray-500 cursor-not-allowed"
}`}
>
Зарегистрироваться
</button>
</form>
);
}
export default RegistrationForm;
Решение 2: С индикатором силы пароля (бонус)
interface PasswordStrength {
level: "слабый" | "средний" | "сильный";
score: number;
}
function getPasswordStrength(password: string): PasswordStrength {
let score = 0;
// Длина
if (password.length >= 8) score += 1;
if (password.length >= 12) score += 1;
if (password.length >= 16) score += 1;
// Буквы
if (/[a-z]/.test(password)) score += 1;
if (/[A-Z]/.test(password)) score += 1;
// Цифры и спецсимволы
if (/\d/.test(password)) score += 1;
if (/[^a-zA-Z\d]/.test(password)) score += 2;
// Определяем уровень
if (score <= 2) return { level: "слабый", score };
if (score <= 4) return { level: "средний", score };
return { level: "сильный", score };
}
function PasswordInput({
value,
onChange,
error,
}: {
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
error?: string;
}) {
const strength = getPasswordStrength(value);
const strengthColors = {
слабый: "bg-red-500",
средний: "bg-yellow-500",
сильный: "bg-green-500",
};
return (
<div className="mb-4">
<label htmlFor="password" className="block font-medium mb-1">
Пароль
</label>
<input
id="password"
name="password"
type="password"
value={value}
onChange={onChange}
className={`w-full px-3 py-2 border rounded ${
error ? "border-red-500" : "border-gray-300"
}`}
/>
{value && (
<div className="mt-2">
<div className="flex gap-1 mb-1">
{[1, 2, 3].map((i) => (
<div
key={i}
className={`flex-1 h-2 rounded ${
strength.score >= i * 2
? strengthColors[strength.level]
: "bg-gray-200"
}`}
/>
))}
</div>
<p className="text-sm">
Сила пароля:{" "}
<span className={`font-medium ${
strength.level === "слабый" ? "text-red-500" :
strength.level === "средний" ? "text-yellow-500" :
"text-green-500"
}`}>
{strength.level}
</span>
</p>
</div>
)}
{error && <p className="text-red-500 text-sm mt-1">{error}</p>}
</div>
);
}
Решение 3: С кастомным хуком для валидации
interface UseFormValidationOptions {
validators: Record<string, (value: string) => string | undefined>;
}
function useFormValidation<T extends Record<string, string>>(
initialValues: T,
options: UseFormValidationOptions
) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState<Record<string, string | undefined>>({});
const validateField = (name: string, value: string) => {
const validator = options.validators[name];
return validator ? validator(value) : undefined;
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setValues(prev => ({ ...prev, [name]: value }));
// Валидация в реальном времени
const error = validateField(name, value);
setErrors(prev => ({ ...prev, [name]: error }));
};
const isFormValid = Object.values(errors).every(err => !err) &&
Object.values(values).every(val => val.length > 0);
return { values, errors, handleChange, isFormValid };
}
// Использование
const validators = {
email: (value: string) => {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return !regex.test(value) ? "Некорректный email" : undefined;
},
password: (value: string) => {
if (value.length < 8) return "Минимум 8 символов";
if (!/[a-zA-Z]/.test(value)) return "Нужны буквы";
if (!/\d/.test(value)) return "Нужны цифры";
return undefined;
},
username: (value: string) => {
if (value.length < 3) return "Минимум 3 символа";
if (value.length > 20) return "Максимум 20 символов";
if (!/^[a-zA-Z0-9_]+$/.test(value)) return "Некорректные символы";
return undefined;
},
};
function FormWithHook() {
const { values, errors, handleChange, isFormValid } = useFormValidation(
{ email: "", username: "", password: "", confirmPassword: "" },
{ validators }
);
return (
<form>
{/* Использование хука */}
</form>
);
}
Лучшие практики
- useMemo — оптимизируем пересчёты валидации
- Реальное время — ошибки показываются при вводе
- Визуальная обратная связь — зелёные галочки и красные границы
- Кастомные хуки — переиспользуемая логика валидации
- Регулярные выражения — правильная валидация формата
Рекомендации для собеседования
- Начните с базовой формы с валидацией
- Объясните логику проверки ошибок
- Добавьте индикатор силы пароля (бонус)
- Покажите кастомный хук (очень хорошо)
- Обсудите UX — когда показывать ошибки, как блокировать отправку
Лучший выбор для production: Вариант с кастомным хуком + индикатор силы пароля — профессионально и переиспользуемо.