Пошук уроків, статей та іншого контенту
Порівняйте контрольовані й неконтрольовані компоненти та оберіть відповідний підхід для роботи з формами й DOM-станом.
У React елемент форми може зберігати своє значення у двох місцях:
у стані React;
безпосередньо в DOM.
Залежно від цього компоненти називають:
контрольованими — React є джерелом актуального значення;
неконтрольованими — значення зберігається в DOM, а React отримує його лише за потреби.
Обидва підходи є правильними. Вибір залежить від того, наскільки часто застосунку потрібно читати та перевіряти значення поля.
Контрольований компонент отримує значення через проп value, а зміни передаються через onChange.
import { useState } from 'react';
function ControlledForm() {
const [email, setEmail] = useState('');
const [message, setMessage] = useState('');
function handleSubmit(event) {
event.preventDefault();
if (!email.includes('@')) {
setMessage('Введіть коректну електронну адресу');
return;
}
setMessage(`Форму надіслано для ${email}`);
}
return (
<form onSubmit={handleSubmit}>
<label>
Електронна пошта:
<input
type="email"
value={email}
onChange={(event) => setEmail(event.target.value)}
/>
</label>
<button type="submit">Надіслати</button>
{message && <p>{message}</p>}
</form>
);
}
export default ControlledForm;У цьому прикладі:
Значення email зберігається у стані React.
Проп value={email} встановлює значення поля.
Після кожної зміни onChange оновлює стан.
При надсиланні форми компонент використовує значення зі стану.
Потік даних виглядає так:
введення користувача → onChange → setEmail → новий стан → valueКонтрольований підхід зручний, коли потрібно:
перевіряти значення під час введення;
показувати помилки валідації;
вимикати або вмикати кнопку залежно від значень;
форматувати введений текст;
синхронізувати кілька полів;
реагувати на кожну зміну значення.
Наприклад, кнопку можна зробити активною лише після введення імені:
import { useState } from 'react';
function NameForm() {
const [name, setName] = useState('');
return (
<form>
<input
value={name}
onChange={(event) => setName(event.target.value)}
placeholder="Ваше ім'я"
/>
<button type="submit" disabled={name.trim() === ''}>
Продовжити
</button>
</form>
);
}
export default NameForm;React у будь-який момент знає поточне значення name, тому може одразу приймати рішення на його основі.
У неконтрольованому компоненті значення зберігається всередині DOM-елемента. React не оновлює стан після кожного введеного символу.
Для початкового значення використовують defaultValue, а для доступу до DOM-елемента — ref.
import { useRef, useState } from 'react';
function UncontrolledForm() {
const emailInputRef = useRef(null);
const [message, setMessage] = useState('');
function handleSubmit(event) {
event.preventDefault();
const email = emailInputRef.current.value;
if (!email.includes('@')) {
setMessage('Введіть коректну електронну адресу');
return;
}
setMessage(`Форму надіслано для ${email}`);
}
return (
<form onSubmit={handleSubmit}>
<label>
Електронна пошта:
<input
ref={emailInputRef}
type="email"
defaultValue=""
/>
</label>
<button type="submit">Надіслати</button>
{message && <p>{message}</p>}
</form>
);
}
export default UncontrolledForm;У цьому прикладі:
defaultValue задає початкове значення поля.
ref зберігає посилання на DOM-елемент.
Під час надсилання форми emailInputRef.current.value читає поточне значення.
React не отримує значення після кожної зміни поля.
Потік даних тут інший:
введення користувача → значення зберігається в DOM → читання через refНеконтрольований підхід може бути доречним, коли:
значення потрібно прочитати лише під час надсилання форми;
форма невелика і не потребує миттєвої валідації;
потрібно мінімізувати кількість стану React;
компонент має взаємодіяти безпосередньо з DOM.
Неконтрольований компонент не означає, що React не може працювати з формою. React усе одно може обробити onSubmit, показати повідомлення або виконати іншу дію. Просто поточне значення поля зберігається не в стані React.
<input
value={value}
onChange={(event) => setValue(event.target.value)}
/>джерело значення — стан React;
значення доступне під час кожного рендерингу;
зручно виконувати валідацію та умовний рендеринг;
потрібно явно обробляти onChange.
<input
ref={inputRef}
defaultValue="Початкове значення"
/>джерело значення — DOM;
значення читають через ref;
зручно для простих форм;
зміни не зберігаються автоматично у стані React.
value і defaultValueЦі пропси мають різне призначення:
value задає поточне значення контрольованого поля;
defaultValue задає лише початкове значення неконтрольованого поля.
// Контрольоване поле
<input value={name} onChange={handleChange} />
// Неконтрольоване поле
<input defaultValue="Іван" ref={nameRef} />Якщо використовується value, поле повинно мати обробник onChange, який оновлює відповідний стан. Інакше поле стане доступним лише для читання.
Для checkbox контрольованим значенням є не value, а проп checked.
import { useState } from 'react';
function AgreementForm() {
const [accepted, setAccepted] = useState(false);
function handleSubmit(event) {
event.preventDefault();
if (!accepted) {
alert('Потрібно прийняти умови');
return;
}
alert('Форму надіслано');
}
return (
<form onSubmit={handleSubmit}>
<label>
<input
type="checkbox"
checked={accepted}
onChange={(event) => setAccepted(event.target.checked)}
/>
Я приймаю умови
</label>
<button type="submit">Зареєструватися</button>
</form>
);
}
export default AgreementForm;Для прапорця потрібно читати event.target.checked, а не event.target.value.
Для неконтрольованого прапорця використовують defaultChecked:
<input
type="checkbox"
defaultChecked={false}
ref={agreementRef}
/>Поточне значення можна прочитати через:
const accepted = agreementRef.current.checked;Оберіть контрольований компонент, якщо:
значення потрібне до надсилання форми;
валідація має працювати під час введення;
інтерфейс залежить від введеного значення;
потрібно програмно змінювати поле;
є кілька полів, значення яких потрібно синхронізувати.
Оберіть неконтрольований компонент, якщо:
значення потрібно прочитати лише під час надсилання;
форма проста;
не потрібно реагувати на кожну зміну;
достатньо початкового значення та читання через ref.
У більшості складних React-форм використовують контрольований підхід, оскільки він робить стан форми явним і передбачуваним. Для простих форм неконтрольований підхід може бути коротшим і зручнішим.
value без onChange<input value={name} />Таке поле не можна редагувати, адже React щоразу встановлює йому значення name.
Правильний варіант:
<input
value={name}
onChange={(event) => setName(event.target.value)}
/>value і defaultValueНе потрібно одночасно використовувати value та defaultValue для одного поля:
<input value={name} defaultValue="Іван" />Оберіть один підхід:
// Контрольований варіант
<input value={name} onChange={handleChange} />
// Неконтрольований варіант
<input defaultValue="Іван" ref={nameRef} />ref до монтування елементаinputRef.current може бути null, якщо DOM-елемент ще не створено. Читати значення через ref зазвичай потрібно в обробнику події, коли елемент уже є на сторінці.
function handleSubmit(event) {
event.preventDefault();
if (inputRef.current) {
console.log(inputRef.current.value);
}
}value замість checkedДля прапорців та перемикачів стан визначається через checked:
<input
type="checkbox"
checked={isAccepted}
onChange={(event) => setIsAccepted(event.target.checked)}
/>null або undefined у valueКонтрольоване поле має отримувати рядкове значення. Для початкового стану краще використати порожній рядок:
const [name, setName] = useState('');Це допомагає уникати перемикання поля між контрольованим і неконтрольованим станом.
Контрольований компонент зберігає значення у стані React.
Для контрольованих текстових полів використовують value і onChange.
Для контрольованих прапорців використовують checked і onChange.
Неконтрольований компонент зберігає значення в DOM.
Для початкового значення неконтрольованого поля використовують defaultValue або defaultChecked.
Поточне значення неконтрольованого поля читають через ref.
Контрольовані компоненти зручні для валідації та складної логіки.
Неконтрольовані компоненти підходять для простих форм, значення яких читають під час надсилання.