React
Формы: controlled и uncontrolled
Форма регистрации, поле поиска, фильтры в каталоге, настройки профиля - ввод данных есть почти в каждом приложении, и именно на формах новички спотыкаются чаще всего. Главный вопрос: кто хранит значение поля - сам DOM или React? От ответа зависит всё: можно ли валидировать ввод на лету, отключать кнопку отправки, форматировать телефон по мере набора. React предлагает два подхода - controlled и uncontrolled - и понимание разницы между ними отделяет форму, которая просто собирает данные, от формы, которая живёт и реагирует на каждый символ.
- Живая валидация (подсветка ошибки сразу при вводе), индикатор силы пароля, счётчик символов - всё это требует контролируемых полей
- Поиск с подсказками по мере набора в Google или GitHub - классический controlled-инпут, читающий значение на каждом onChange
- Простые формы с одной кнопкой отправки часто делают uncontrolled через ref - меньше ре-рендеров и кода
- React 19 добавил form actions и хуки useActionState, useFormStatus - новый способ обрабатывать отправку форм, в том числе на сервере
Предварительные знания
- События и обработчики: onChange, аргумент события, event.target.value, preventDefault
- useState: хранение и иммутабельное обновление значения, в том числе объекта
- Идея односторонего потока данных: значение спускается вниз, событие идёт наверх
Контролируемые поля: value и onChange
В контролируемом поле источник правды - состояние React, а не DOM. Полю задают два атрибута: value привязывает его к переменной состояния, а onChange при каждом изменении пишет новое значение в состояние. Получается замкнутый цикл: пользователь печатает, onChange обновляет состояние, состояние возвращается в value, поле перерисовывается. DOM никогда не хранит значение сам - он лишь отображает то, что в состоянии.
Раз значение живёт в состоянии, с ним можно делать что угодно прямо во время ввода: считать длину, валидировать, форматировать, отключать кнопку. В примере счётчик символов и блокировка кнопки работают на каждый набранный символ, потому что состояние - единый источник правды для всего этого.
Если задать value без onChange, поле станет доступным только для чтения: пользователь не сможет в нём печатать, а React в режиме разработки выдаст предупреждение. Контролируемое поле требует обоих атрибутов вместе.
Где хранится значение контролируемого поля?
Неконтролируемые поля: defaultValue и ref
В неконтролируемом поле значение хранит сам DOM, как в обычном HTML. React не привязывается к каждому нажатию клавиши. Начальное значение задают через defaultValue (не value), а прочитать итог можно при необходимости - например, при сабмите - через ref, ссылку на DOM-узел. Такой подход проще и вызывает меньше ре-рендеров, потому что состояние не обновляется на каждый символ.
- Controlled — Значение в состоянии (value + onChange). Нужен для живой валидации, форматирования, зависимой логики. Ре-рендер на каждый ввод
- Uncontrolled — Значение в DOM (defaultValue + ref). Проще и легче для разовых форм, где значение нужно только при отправке
Официальная рекомендация React - предпочитать контролируемые поля для большинства случаев, потому что они дают полный контроль над значением. Неконтролируемые уместны для простых форм, интеграции со сторонним кодом и файловых input type=file, значение которых задать программно нельзя.
Когда уместно неконтролируемое поле с ref вместо контролируемого?
Много полей и form actions в React 19
Заводить отдельный useState на каждое поле большой формы утомительно. Удобнее хранить все значения в одном объекте состояния и обновлять его одним обработчиком. Ключом служит атрибут name поля, а обновление делают иммутабельно через computed property name: создаётся новый объект, в котором переписано только изменившееся поле.
React 19 добавил новый, более декларативный путь для отправки форм - form actions. Атрибут action у тега form принимает функцию, которой React передаёт FormData со всеми полями. Хук useActionState управляет состоянием отправки и результатом, а useFormStatus даёт дочерним компонентам знать, идёт ли отправка прямо сейчас. Этот же механизм работает и с серверными функциями, объединяя клиент и сервер.
Form actions не отменяют контролируемые поля - это дополнительный инструмент, особенно сильный с серверными функциями и прогрессивным улучшением. Для живой валидации на лету контролируемое состояние по-прежнему остаётся основным подходом.
Как удобнее всего обработать множество полей одной формы одним обработчиком?
Связь с другими темами
Формы - концентрат всего, что было в модуле: состояние, события, поток данных:
- События и обработчики — onChange и чтение event.target.value - основа любого контролируемого поля
- Подъём состояния — Состояние формы поднимают к родителю, когда от него зависят соседние компоненты
- useState — Значения контролируемых полей хранятся и обновляются через состояние
Итог
- Контролируемое поле: значение хранит React (value + onChange). Источник правды - состояние, поле всегда отражает его
- Неконтролируемое поле: значение хранит DOM (defaultValue), а React читает его при необходимости через ref
- Controlled нужен, когда значение участвует в логике: валидация на лету, форматирование, отключение кнопки
- Uncontrolled проще и легче для разовых форм, где значение нужно только в момент отправки
- Множество полей удобно вести одним обработчиком через атрибут name и иммутабельное обновление объекта
- React 19 добавил form actions и useActionState - декларативный способ обработки отправки, в том числе серверной
Связанные уроки
- rc-07-events-handlers — Контролируемое поле целиком строится на onChange и аргументе события из урока про события
- rc-06-usestate — Значение контролируемого поля хранится в useState, поэтому состояние нужно понимать заранее
- rc-09-lifting-state — Состояние формы часто поднимают к родителю, чтобы несколько полей и кнопок видели одни данные