</> Praktum

Главная / React / Формы в React

Справочник · React

Формы в React: состояние полей и отправка

Редакция PraktumОбновлено 6 мин чтенияУровень: новичок

В 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, textareavaluee.target.value
checkboxcheckede.target.checked
radiochecked={plan === 'pro'}e.target.value
selectvalue на selecte.target.value
numbervaluee.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".

Управляемые или неуправляемые

Управляемые

Проверка на лету, форматирование, зависимые поля, блокировка кнопки.

Неуправляемые + FormData

Простые формы: собрать данные при отправке, меньше кода.

Частые ошибки

  • Задают 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 при отправке.

Источники

Ещё в разделе React