В React существуют два подхода к работе с формами.
Значение инпута хранится в state и синхронизируется через value + onChange. React является "источником истины":
function ControlledInput() {
const [name, setName] = useState('')
return (
<input
value={name} // State -> DOM
onChange={e => setName(e.target.value)} // DOM -> State
/>
)
}Это рекомендуемый подход: значение всегда доступно в state, можно валидировать на лету, форматировать ввод.
Значение хранится в DOM, доступ через useRef:
function UncontrolledInput() {
const inputRef = useRef(null)
function handleSubmit() {
console.log(inputRef.current.value) // читаем из DOM напрямую
}
return <input ref={inputRef} defaultValue="начальное" />
}Используется реже: для интеграции с не-React библиотеками, файловых инпутов, когда performance критичен.
Вместо отдельного обработчика для каждого поля — один общий:
function RegistrationForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
password: '',
})
// Один обработчик для всех полей!
function handleChange(e) {
const { name, value } = e.target
setFormData(prev => ({
...prev,
[name]: value // вычисляемое имя свойства
}))
}
return (
<form>
<input name="name" value={formData.name} onChange={handleChange} />
<input name="email" value={formData.email} onChange={handleChange} />
<input name="password" value={formData.password} onChange={handleChange} />
</form>
)
}Ключ — атрибут name у инпута и вычисляемое свойство [name]: value.
function RegistrationForm() {
const [formData, setFormData] = useState({ name: '', email: '', password: '' })
const [errors, setErrors] = useState({})
function validate(data) {
const errs = {}
if (!data.name.trim()) {
errs.name = 'Имя обязательно'
}
if (!data.email.trim()) {
errs.email = 'Email обязателен'
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
errs.email = 'Некорректный email'
}
if (!data.password) {
errs.password = 'Пароль обязателен'
} else if (data.password.length < 6) {
errs.password = 'Пароль минимум 6 символов'
}
return errs
}
function handleSubmit(e) {
e.preventDefault()
const errs = validate(formData)
if (Object.keys(errs).length > 0) {
setErrors(errs)
return
}
// Форма валидна — отправляем
console.log('Отправка:', formData)
}
return (
<form onSubmit={handleSubmit}>
<div>
<input name="name" value={formData.name} onChange={handleChange} />
{errors.name && <span className="error">{errors.name}</span>}
</div>
<button type="submit">Зарегистрироваться</button>
</form>
)
}textarea — в React самозакрывающийся с value:
<textarea value={text} onChange={e => setText(e.target.value)} />
// В HTML textarea использует содержимое: <textarea>текст</textarea>select:
<select value={selected} onChange={e => setSelected(e.target.value)}>
<option value="ru">Русский</option>
<option value="en">English</option>
</select>checkbox:
<input
type="checkbox"
checked={isChecked} // не value, а checked!
onChange={e => setChecked(e.target.checked)}
/>interface FormData {
name: string
email: string
password: string
}
interface FormErrors {
name?: string
email?: string
password?: string
}
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
const { name, value } = e.target
setFormData(prev => ({ ...prev, [name]: value }))
}В реальных проектах часто используют:
Но понимание базового паттерна controlled inputs обязательно.
Контролируемые поля, единый обработчик onChange и валидация формы регистрации
// Реализуем логику формы регистрации на чистом JS
// без DOM — только бизнес-логика, как она работает в React
// ============================================================
// Контролируемая форма — state как источник истины
// ============================================================
function createRegistrationForm() {
// Аналог: const [formData, setFormData] = useState({...})
let formData = { name: '', email: '', password: '', role: 'user' }
let errors = {}
let submitted = false
// Единый обработчик для всех полей
// Аналог React: (e) => setFormData(prev => ({ ...prev, [e.target.name]: e.target.value }))
function handleChange(fieldName, value) {
// Вычисляемое имя свойства — тот же паттерн что в React
formData = { ...formData, [fieldName]: value }
// Валидация в реальном времени (если уже пытались отправить)
if (submitted) {
errors = validate(formData)
}
console.log(`[onChange] ${fieldName}: "${value}"`)
}
// ============================================================
// Валидация
// ============================================================
function validate(data) {
const errs = {}
// Имя обязательно
if (!data.name.trim()) {
errs.name = 'Имя обязательно'
} else if (data.name.trim().length < 2) {
errs.name = 'Имя минимум 2 символа'
}
// Email: обязателен + формат
if (!data.email.trim()) {
errs.email = 'Email обязателен'
} else if (!/^[^s@]+@[^s@]+.[^s@]+$/.test(data.email)) {
errs.email = 'Некорректный формат email'
}
// Пароль: обязателен + минимум 6 символов
if (!data.password) {
errs.password = 'Пароль обязателен'
} else if (data.password.length < 6) {
errs.password = `Пароль минимум 6 символов (сейчас ${data.password.length})`
}
return errs
}
function handleSubmit() {
submitted = true
errors = validate(formData)
if (Object.keys(errors).length > 0) {
console.log('[submit] ОШИБКИ ВАЛИДАЦИИ:', errors)
return { success: false, errors }
}
console.log('[submit] Форма валидна! Данные:', {
name: formData.name,
email: formData.email,
role: formData.role
// password не логируем в реальном приложении!
})
return { success: true, data: formData }
}
return { handleChange, handleSubmit, getState: () => ({ formData: {...formData}, errors: {...errors} }) }
}
// ============================================================
// Демонстрация работы формы
// ============================================================
const form = createRegistrationForm()
console.log('=== Попытка отправить пустую форму ===')
const result1 = form.handleSubmit()
console.log('Ошибок:', Object.keys(result1.errors).length) // 3
console.log('
=== Заполняем поля ===')
form.handleChange('name', 'Алексей')
form.handleChange('email', 'не-email') // некорректный
form.handleChange('password', '123') // слишком короткий
console.log('
=== Снова пробуем отправить ===')
const result2 = form.handleSubmit()
console.log('Ошибка email:', result2.errors.email)
console.log('Ошибка password:', result2.errors.password)
console.log('
=== Исправляем ошибки ===')
form.handleChange('email', 'alex@example.com')
form.handleChange('password', 'secret123')
console.log('
=== Финальная отправка ===')
const result3 = form.handleSubmit()
console.log('Успех:', result3.success) // trueВ React существуют два подхода к работе с формами.
Значение инпута хранится в state и синхронизируется через value + onChange. React является "источником истины":
function ControlledInput() {
const [name, setName] = useState('')
return (
<input
value={name} // State -> DOM
onChange={e => setName(e.target.value)} // DOM -> State
/>
)
}Это рекомендуемый подход: значение всегда доступно в state, можно валидировать на лету, форматировать ввод.
Значение хранится в DOM, доступ через useRef:
function UncontrolledInput() {
const inputRef = useRef(null)
function handleSubmit() {
console.log(inputRef.current.value) // читаем из DOM напрямую
}
return <input ref={inputRef} defaultValue="начальное" />
}Используется реже: для интеграции с не-React библиотеками, файловых инпутов, когда performance критичен.
Вместо отдельного обработчика для каждого поля — один общий:
function RegistrationForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
password: '',
})
// Один обработчик для всех полей!
function handleChange(e) {
const { name, value } = e.target
setFormData(prev => ({
...prev,
[name]: value // вычисляемое имя свойства
}))
}
return (
<form>
<input name="name" value={formData.name} onChange={handleChange} />
<input name="email" value={formData.email} onChange={handleChange} />
<input name="password" value={formData.password} onChange={handleChange} />
</form>
)
}Ключ — атрибут name у инпута и вычисляемое свойство [name]: value.
function RegistrationForm() {
const [formData, setFormData] = useState({ name: '', email: '', password: '' })
const [errors, setErrors] = useState({})
function validate(data) {
const errs = {}
if (!data.name.trim()) {
errs.name = 'Имя обязательно'
}
if (!data.email.trim()) {
errs.email = 'Email обязателен'
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
errs.email = 'Некорректный email'
}
if (!data.password) {
errs.password = 'Пароль обязателен'
} else if (data.password.length < 6) {
errs.password = 'Пароль минимум 6 символов'
}
return errs
}
function handleSubmit(e) {
e.preventDefault()
const errs = validate(formData)
if (Object.keys(errs).length > 0) {
setErrors(errs)
return
}
// Форма валидна — отправляем
console.log('Отправка:', formData)
}
return (
<form onSubmit={handleSubmit}>
<div>
<input name="name" value={formData.name} onChange={handleChange} />
{errors.name && <span className="error">{errors.name}</span>}
</div>
<button type="submit">Зарегистрироваться</button>
</form>
)
}textarea — в React самозакрывающийся с value:
<textarea value={text} onChange={e => setText(e.target.value)} />
// В HTML textarea использует содержимое: <textarea>текст</textarea>select:
<select value={selected} onChange={e => setSelected(e.target.value)}>
<option value="ru">Русский</option>
<option value="en">English</option>
</select>checkbox:
<input
type="checkbox"
checked={isChecked} // не value, а checked!
onChange={e => setChecked(e.target.checked)}
/>interface FormData {
name: string
email: string
password: string
}
interface FormErrors {
name?: string
email?: string
password?: string
}
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
const { name, value } = e.target
setFormData(prev => ({ ...prev, [name]: value }))
}В реальных проектах часто используют:
Но понимание базового паттерна controlled inputs обязательно.
Контролируемые поля, единый обработчик onChange и валидация формы регистрации
// Реализуем логику формы регистрации на чистом JS
// без DOM — только бизнес-логика, как она работает в React
// ============================================================
// Контролируемая форма — state как источник истины
// ============================================================
function createRegistrationForm() {
// Аналог: const [formData, setFormData] = useState({...})
let formData = { name: '', email: '', password: '', role: 'user' }
let errors = {}
let submitted = false
// Единый обработчик для всех полей
// Аналог React: (e) => setFormData(prev => ({ ...prev, [e.target.name]: e.target.value }))
function handleChange(fieldName, value) {
// Вычисляемое имя свойства — тот же паттерн что в React
formData = { ...formData, [fieldName]: value }
// Валидация в реальном времени (если уже пытались отправить)
if (submitted) {
errors = validate(formData)
}
console.log(`[onChange] ${fieldName}: "${value}"`)
}
// ============================================================
// Валидация
// ============================================================
function validate(data) {
const errs = {}
// Имя обязательно
if (!data.name.trim()) {
errs.name = 'Имя обязательно'
} else if (data.name.trim().length < 2) {
errs.name = 'Имя минимум 2 символа'
}
// Email: обязателен + формат
if (!data.email.trim()) {
errs.email = 'Email обязателен'
} else if (!/^[^s@]+@[^s@]+.[^s@]+$/.test(data.email)) {
errs.email = 'Некорректный формат email'
}
// Пароль: обязателен + минимум 6 символов
if (!data.password) {
errs.password = 'Пароль обязателен'
} else if (data.password.length < 6) {
errs.password = `Пароль минимум 6 символов (сейчас ${data.password.length})`
}
return errs
}
function handleSubmit() {
submitted = true
errors = validate(formData)
if (Object.keys(errors).length > 0) {
console.log('[submit] ОШИБКИ ВАЛИДАЦИИ:', errors)
return { success: false, errors }
}
console.log('[submit] Форма валидна! Данные:', {
name: formData.name,
email: formData.email,
role: formData.role
// password не логируем в реальном приложении!
})
return { success: true, data: formData }
}
return { handleChange, handleSubmit, getState: () => ({ formData: {...formData}, errors: {...errors} }) }
}
// ============================================================
// Демонстрация работы формы
// ============================================================
const form = createRegistrationForm()
console.log('=== Попытка отправить пустую форму ===')
const result1 = form.handleSubmit()
console.log('Ошибок:', Object.keys(result1.errors).length) // 3
console.log('
=== Заполняем поля ===')
form.handleChange('name', 'Алексей')
form.handleChange('email', 'не-email') // некорректный
form.handleChange('password', '123') // слишком короткий
console.log('
=== Снова пробуем отправить ===')
const result2 = form.handleSubmit()
console.log('Ошибка email:', result2.errors.email)
console.log('Ошибка password:', result2.errors.password)
console.log('
=== Исправляем ошибки ===')
form.handleChange('email', 'alex@example.com')
form.handleChange('password', 'secret123')
console.log('
=== Финальная отправка ===')
const result3 = form.handleSubmit()
console.log('Успех:', result3.success) // trueСоздай компонент App с формой обратной связи. Форма содержит поля "Имя" и "Сообщение" (textarea). Используй useState для хранения значений полей. При отправке формы (onSubmit) — проверяй что оба поля не пустые, если нет — показывай ошибку, если да — показывай сообщение об успехе и очищай форму.
e.preventDefault() предотвращает перезагрузку. Проверка: name.trim() === "" || message.trim() === "". onChange для textarea: e => setMessage(e.target.value). При успехе setSubmitted(true). Кнопка "Отправить ещё": setSubmitted(false).