Контролируемый компонент это элемент формы, значение которого живёт в состоянии React: вы передаёте value и обновляете состояние в onChange, поэтому React решает, что показывает поле. Неконтролируемый компонент хранит значение в DOM, как в обычном HTML: вы передаёте начальное defaultValue и позже читаете текущее значение через ref или FormData.
Пишите в оба поля. Абзац следует за первым при каждом нажатии клавиши, потому что каждое изменение устанавливает состояние и вызывает новый рендер. Второе поле меняется только в DOM; React не рендерит, и его значение вы видите, только когда его запрашивает «Read both».
Контролируемые поля
В контролируемом поле состояние React это единственный источник истины. При каждом нажатии клавиши происходит следующее:
- Пользователь печатает, и браузер вызывает событие input.
- Ваш обработчик
onChangeчитаетe.target.valueи вызывает сеттер. - React рендерит заново и записывает новое состояние обратно в
valueполя.
Поскольку ваш код стоит посреди этого цикла, можно делать то, чего не умеет неконтролируемое поле: отклонять или преобразовывать символы, показывать значение в другом месте, отключать кнопку, пока значение не станет корректным, или задавать значение из кода (кнопка «Clear», подсказка, на которую нажимает пользователь).
Попробуйте ввести буквы: они не появятся, потому что обработчик удаляет всё, что не является цифрой, до установки состояния. Кнопка Verify остаётся отключённой, пока не будет ровно шесть цифр.
Именно контролируемые поля используют примеры на странице о формах, и это выбор по умолчанию в большей части кода на React.
Неконтролируемые поля
Неконтролируемое поле работает как обычный HTML. React рендерит его один раз с defaultValue (или defaultChecked для флажков и переключателей), а потом оставляет в покое. Чтобы прочитать значение, либо храните ref на элемент, либо при отправке прочитайте все именованные поля формы через FormData.
Для полей нет ни состояния, ни onChange. Значения собираются только при отправке формы. Снимите флажок и отправьте снова: ключ subscribe исчезнет, потому что неотмеченный флажок вообще не входит в FormData.
Изменение defaultValue после первого рендера никак не влияет на поле, потому что React применяет его только при создании элемента. Чтобы сбросить неконтролируемую форму, вызовите e.target.reset() или дайте форме новый key, чтобы React создал свежие поля.
Поле, в котором нельзя печатать
Если передать value без onChange, поле контролируемое, но состояние ничто не обновляет. React после каждого нажатия клавиши записывает обратно то же значение, поэтому поле выглядит застывшим.
Попробуйте печатать в каждом поле. Первое не меняется. Второе показывает, как исправить контролируемое поле, и пока вы в нём печатаете, первое и последнее поля следуют за ним, потому что все три читают одно и то же состояние. Третье принимает ввод, потому что оно неконтролируемое. Четвёртое намеренно сообщает, что его нельзя редактировать.
В разработке React выводит предупреждение для первого поля: "You provided a value prop to a form field without an onChange handler". Превью работает как продакшен-сборка, поэтому здесь вы этого предупреждения не увидите, а на своём dev-сервере увидите.
Переход от неконтролируемого к контролируемому
React решает, контролируемое ли поле, глядя на value. Если value равно undefined или null, поле неконтролируемое. Поэтому поле, value которого сначала undefined, а потом становится строкой, за время жизни переключается с неконтролируемого на контролируемое, и в разработке React предупреждает: "A component is changing an uncontrolled input to be controlled".
Предупреждение указывает на настоящую ошибку. Обратный переход, от строки к undefined, снова делает поле неконтролируемым, и React перестаёт в него записывать.
Напишите что-нибудь в оба поля и нажмите каждую кнопку Clear. Второе поле опустеет. Первое сохранит текст: установка состояния в undefined сделала его неконтролируемым, поэтому React больше не управляет тем, что оно показывает.
Обычный источник undefined это данные, которые ещё не загрузились (value={user.name} до появления пользователя), или поле объекта, которого ещё нет. Два способа исправить:
const [name, setName] = useState(''); // start with a string
<input value={user?.name ?? ''} /> // or fall back to one
Для флажков то же правило действует для checked: начинайте с false, а не с undefined.
Файловые поля всегда неконтролируемые
<input type="file"> нельзя сделать контролируемым, потому что браузеры не позволяют странице задавать его значение (страница, которая могла бы выбирать файлы на вашем диске, была бы дырой в безопасности). Читайте выбранные файлы из e.target.files в onChange или через ref при отправке формы.
Выберите один или два файла на компьютере; ничего не загружается, пример только перечисляет имена и размеры. Единственное значение, которое можно задать файловому полю, это пустая строка, и именно так кнопка Clear его сбрасывает.
Когда что выбирать
| Контролируемые | Неконтролируемые | |
|---|---|---|
| Где живёт значение | Состояние React | DOM |
| Начальное значение | value / checked | defaultValue / defaultChecked |
| Чтение значения | В любой момент, из состояния | Через ref или FormData при отправке |
| Рендер при каждом нажатии клавиши | Да | Нет |
| Изменение значения из кода | Установить состояние | Записать в DOM через ref |
| Подходят для | Живой валидации, форматирования, зависимых полей | Простых форм, читаемых при отправке, файловых полей, действий форм |
Выбирайте контролируемые, когда экран должен реагировать на значение во время ввода. Выбирайте неконтролируемые, когда значения нужны только в конце; так же работают и действия форм в React 19: <form action={fn}> передаёт вашей функции FormData и после этого сбрасывает неконтролируемые поля.
Рендер при каждом нажатии клавиши звучит дорого, но для обычной формы это не проблема. Проблемой он становится, только когда вместе с полем перерендеривается большое медленное дерево; тогда держите состояние рядом с полем или вынесите медленную часть в отдельный компонент.
Те же два слова описывают и ваши собственные компоненты. Компонент, который получает значение и onChange от родителя через пропсы, контролируется родителем; компонент, который хранит собственное состояние, неконтролируемый. Перенос состояния вверх, чтобы сделать компонент контролируемым, это и есть подъём состояния.
Часто задаваемые вопросы
Что такое контролируемый компонент в React?
Элемент формы, текущее значение которого берётся из состояния React, задаётся через value (или checked) и обновляется через onChange. Состояние React это единственный источник истины, поэтому вы всегда знаете значение и можете менять его из кода.
Что такое неконтролируемый компонент в React?
Элемент формы, который хранит своё значение в DOM, как в обычном HTML. Вы даёте ему начальное значение через defaultValue (или defaultChecked) и читаете текущее значение, когда нужно, через ref или FormData при отправке.
Почему я не могу печатать в поле ввода React?
Вы передали value без onChange. Поле контролируемое, поэтому React после каждого нажатия клавиши возвращает в него значение из состояния. Добавьте onChange, который обновляет состояние, перейдите на defaultValue или добавьте readOnly, если поле действительно не должно меняться.
Как исправить "A component is changing an uncontrolled input to be controlled"?
value поля начиналось как undefined (или null) и позже стало строкой. Следите, чтобы оно всегда было строкой: начинайте состояние с '' или пишите value={name ?? ''}.
Какие поля использовать: контролируемые или неконтролируемые?
Используйте контролируемые поля, когда интерфейс реагирует на значение во время ввода: валидация, форматирование, отключённая кнопка, поля, зависящие друг от друга. Используйте неконтролируемые, когда значения нужны только при отправке, а также для файловых полей, которые всегда неконтролируемые.