Неконтрольоване поле зберігає значення саме, у DOM. React лише задає початкове значення:
<input name="title" defaultValue="Чернетка" />
Значення читають у момент відправки - з FormData чи через ref.
Контрольоване поле - значення живе в стані React, а поле лише показує його:
const [title, setTitle] = useState('');
<input value={title} onChange={(e) => setTitle(e.target.value)} />
Кожне натискання клавіші - onChange → новий стан → новий рендер з новим value.
Коли контрольоване:
- значення потрібне під час введення: лічильник символів, пошук «на льоту», валідація в реальному часі;
- поля залежать одне від одного (вибір країни змінює список міст);
- значення треба форматувати чи обмежувати (лише цифри, верхній регістр);
- кнопка «Зберегти» активна лише при зміненій формі.
Коли неконтрольоване:
- значення потрібне лише при відправці - звичайна форма з
<form action>чиonSubmitіFormData; - великі форми, де рендер на кожну літеру помітно гальмує;
<input type="file">- завжди неконтрольоване: значення файлового поля задати з коду не можна.
Правила, які не можна порушувати:
- поле не може бути одночасно контрольованим і неконтрольованим, і не повинне перемикатися між режимами. Типова причина -
value={user.name}, деnameспершуundefined: поле стартує неконтрольованим, а потім стає контрольованим. Початкове значення - порожній рядок'', а неnullчиundefined; - для чекбоксів -
checked/defaultChecked, а неvalue.
Бібліотеки форм (React Hook Form) за замовчуванням працюють з неконтрольованими полями саме заради продуктивності, а стан помилок і «брудності» ведуть окремо.
Докладніше в документації: input: керування полем через стан