Справочник · React
Формы в React: состояние полей и отправка
В React поля формы обычно делают управляемыми: значение хранится в состоянии и передаётся в value, а onChange обновляет состояние при каждом вводе. Так React всегда знает, что введено, и может проверять, форматировать и блокировать отправку. Отправку обрабатывают в onSubmit формы с e.preventDefault(). Для простых форм подойдут и неуправляемые поля с FormData.
value={name} onChange={(e) => setName(e.target.value)}.- Чекбокс —
checked, а не value. - onSubmit у формы, а не onClick у кнопки.
- Один обработчик по
e.target.nameдля многих полей. - Без onChange управляемое поле станет «только для чтения».
Минимальный пример
function Subscribe() {
const [email, setEmail] = useState('');
const [status, setStatus] = useState('');
async function handleSubmit(e) {
e.preventDefault();
const res = await fetch('/api/subscribe', { method: 'POST', body: JSON.stringify({ email }) });
setStatus(res.ok ? 'Готово!' : 'Ошибка, попробуйте ещё раз');
}
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email">Email</label>
<input id="email" type="email" required value={email} onChange={(e) => setEmail(e.target.value)} />
<button>Подписаться</button>
<p role="status">{status}</p>
</form>
);
}Значение поля живёт в состоянии email. В JSX атрибут for пишется как htmlFor. Обработчик onSubmit срабатывает и по кнопке, и по Enter.
Разные типы полей
| Поле | Значение | Из события |
|---|---|---|
| input text, email, textarea | value | e.target.value |
| checkbox | checked | e.target.checked |
| radio | checked={plan === 'pro'} | e.target.value |
| select | value на select | e.target.value |
| number | value | e.target.valueAsNumber или Number() |
В React у textarea значение задаётся атрибутом value, а у select — value на самом select, а не selected у option.
Один обработчик для многих полей
const [form, setForm] = useState({ name: '', phone: '', agree: false });
function handleChange(e) {
const { name, value, type, checked } = e.target;
setForm((prev) => ({ ...prev, [name]: type === 'checkbox' ? checked : value }));
}
<input name="name" value={form.name} onChange={handleChange} />
<input name="phone" value={form.phone} onChange={handleChange} />
<input name="agree" type="checkbox" checked={form.agree} onChange={handleChange} />Состояние формы — один объект, а обработчик обновляет поле по атрибуту name. Spread ...prev сохраняет остальные поля — состояние нельзя мутировать напрямую.
Проверка и ошибки
const [touched, setTouched] = useState(false);
const error = phone.replace(/\D/g, '').length < 11 ? 'Введите телефон полностью' : '';
<label htmlFor="phone">Телефон</label>
<input
id="phone"
value={phone}
onChange={(e) => setPhone(e.target.value)}
onBlur={() => setTouched(true)}
aria-invalid={touched && Boolean(error)}
aria-describedby="phone-error"
/>
<p id="phone-error">{touched && error}</p>Ошибку вычисляют из текущего значения, а показывают после того, как поле потеряло фокус, — не нужно ругать человека на первой букве. aria-invalid и aria-describedby связывают ошибку с полем для скринридера.
Неуправляемые поля и FormData
function Feedback() {
function handleSubmit(e) {
e.preventDefault();
const data = Object.fromEntries(new FormData(e.currentTarget));
send(data);
e.currentTarget.reset();
}
return (
<form onSubmit={handleSubmit}>
<input name="name" defaultValue="" />
<textarea name="message" />
<button>Отправить</button>
</form>
);
}Если не нужно реагировать на каждый ввод, поля можно оставить неуправляемыми: начальное значение — defaultValue, данные — через FormData при отправке. Меньше кода и перерисовок. В React 19 форма также принимает функцию в action.
Предупреждение о value без onChange
<input value={name} /> // поле не редактируется, React предупредит
<input value={name} onChange={handleChange} /> // управляемое
<input defaultValue={name} /> // неуправляемое с начальным значениемЕсли задать value без onChange, React фиксирует значение, и ввод не работает. Либо добавь onChange, либо используй defaultValue, либо readOnly, если поле действительно только для чтения.
Состояние отправки
const [pending, setPending] = useState(false);
async function handleSubmit(e) {
e.preventDefault();
setPending(true);
try { await send(form); setStatus('Отправлено'); }
catch { setStatus('Ошибка сети'); }
finally { setPending(false); }
}
<button disabled={pending}>{pending ? 'Отправляем…' : 'Отправить'}</button>Блокировка кнопки на время запроса защищает от двойной отправки. Статус результата выводи в элемент с role="status".
Управляемые или неуправляемые
Проверка на лету, форматирование, зависимые поля, блокировка кнопки.
Простые формы: собрать данные при отправке, меньше кода.
Частые ошибки
- Задают value без onChange, и поле не редактируется.
- Используют value у чекбокса вместо checked.
- Мутируют объект состояния формы напрямую.
- Вешают отправку на onClick кнопки вместо onSubmit формы.
- Показывают ошибки до того, как человек закончил ввод.
Доступность
У каждого поля — label с htmlFor. Ошибки связывай через aria-describedby и отмечай aria-invalid, результат отправки выводи в role="status". После неудачной отправки ставь фокус на первое поле с ошибкой.
Поддержка браузерами
Управляемые и неуправляемые поля работают во всех версиях React. Атрибут action с функцией у form — в React 19.
Практика
Сделай форму заказа звонка: имя, телефон, выбор времени (select: утро, день, вечер) и чекбокс согласия. Кнопка «Заказать» неактивна, пока не заполнены имя, телефон из 11 цифр и не отмечено согласие. Используй один обработчик onChange.
Показать решение
function Callback() {
const [form, setForm] = useState({ name: '', phone: '', time: 'day', agree: false });
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setForm((p) => ({ ...p, [name]: type === 'checkbox' ? checked : value }));
};
const valid = form.name.trim() && form.phone.replace(/\D/g, '').length === 11 && form.agree;
return (
<form onSubmit={(e) => { e.preventDefault(); send(form); }}>
<label>Имя <input name="name" value={form.name} onChange={handleChange} /></label>
<label>Телефон <input name="phone" type="tel" value={form.phone} onChange={handleChange} /></label>
<label>Когда позвонить
<select name="time" value={form.time} onChange={handleChange}>
<option value="morning">Утро</option>
<option value="day">День</option>
<option value="evening">Вечер</option>
</select>
</label>
<label><input name="agree" type="checkbox" checked={form.agree} onChange={handleChange} /> Согласен на обработку данных</label>
<button disabled={!valid}>Заказать</button>
</form>
);
}Частые вопросы
Что такое управляемое поле в React?
Поле, значение которого хранится в состоянии и задаётся через value с обработчиком onChange.
Почему в поле нельзя ничего ввести?
Задан value без onChange. Добавь обработчик или используй defaultValue.
Как обработать отправку формы в React?
onSubmit у формы и e.preventDefault() в обработчике.
Как работать с чекбоксом?
Через checked и e.target.checked.
Можно ли обойтись без состояния для полей?
Да, неуправляемые поля и FormData при отправке.