Пошук уроків, статей та іншого контенту
Порівняйте контрольований і неконтрольований підходи та оберіть відповідний спосіб керування станом форми.
У React є два основні способи роботи з полями форми:
контрольований підхід — значення поля зберігається у стані React;
неконтрольований підхід — значення зберігається безпосередньо в DOM, а React отримує його лише за потреби.
Обидва підходи є правильними. Вибір залежить від того, наскільки активно компоненту потрібно реагувати на зміни введених даних.
У контрольованому полі значення атрибута value надходить зі стану React.
Коли користувач вводить текст:
спрацьовує подія onChange;
обробник оновлює стан;
React повторно відображає компонент;
поле отримує нове значення зі стану.
import { useState } from "react";
export default function ControlledForm() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [message, setMessage] = useState("");
function handleSubmit(event) {
event.preventDefault();
setMessage(`Дані відправлено для ${name}: ${email}`);
}
return (
<form onSubmit={handleSubmit}>
<label>
Ім’я:
<input
type="text"
value={name}
onChange={(event) => setName(event.target.value)}
/>
</label>
<label>
Email:
<input
type="email"
value={email}
onChange={(event) => setEmail(event.target.value)}
/>
</label>
<button type="submit">Надіслати</button>
{message && <p>{message}</p>}
</form>
);
}У цьому прикладі змінні name і email є єдиним джерелом правди для полів форми.
Контрольовані поля зручні, коли потрібно:
перевіряти дані під час введення;
вимикати кнопку за певної умови;
показувати кількість символів;
форматувати введене значення;
показувати або приховувати частини форми;
синхронізувати дані з іншими компонентами;
змінювати значення поля програмно.
Наприклад, кнопку можна зробити доступною лише після введення імені:
import { useState } from "react";
export default function NameForm() {
const [name, setName] = useState("");
return (
<form>
<label>
Ім’я:
<input
value={name}
onChange={(event) => setName(event.target.value)}
/>
</label>
<button type="submit" disabled={name.trim() === ""}>
Надіслати
</button>
</form>
);
}Оскільки React знає поточне значення name, він може одразу визначити, чи повинна кнопка бути активною.
У неконтрольованому підході React не зберігає поточне значення поля у стані. Значення зберігається самим DOM.
Для доступу до поля використовують useRef. Значення зазвичай читають під час відправлення форми.
import { useRef, useState } from "react";
export default function UncontrolledForm() {
const nameInputRef = useRef(null);
const emailInputRef = useRef(null);
const [message, setMessage] = useState("");
function handleSubmit(event) {
event.preventDefault();
const name = nameInputRef.current.value;
const email = emailInputRef.current.value;
setMessage(`Дані відправлено для ${name}: ${email}`);
}
return (
<form onSubmit={handleSubmit}>
<label>
Ім’я:
<input type="text" ref={nameInputRef} defaultValue="" />
</label>
<label>
Email:
<input type="email" ref={emailInputRef} defaultValue="" />
</label>
<button type="submit">Надіслати</button>
{message && <p>{message}</p>}
</form>
);
}У цьому прикладі введення тексту не оновлює стан React. Коли користувач натискає кнопку, обробник отримує значення через:
nameInputRef.current.valuedefaultValue замість valueДля неконтрольованих полів початкове значення задають за допомогою defaultValue:
<input defaultValue="Початковий текст" />defaultValue встановлює значення лише під час початкового створення поля. Подальші зміни в DOM відбуваються без керування React.
Якщо використати value, поле стане контрольованим і потребуватиме обробника onChange:
<input value={name} onChange={(event) => setName(event.target.value)} />const [value, setValue] = useState("");
<input
value={value}
onChange={(event) => setValue(event.target.value)}
/>React зберігає значення і керує ним.
Підходить, якщо:
значення потрібне під час введення;
необхідна миттєва валідація;
інтерфейс залежить від введених даних;
потрібно програмно змінювати поле.
const inputRef = useRef(null);
<input ref={inputRef} defaultValue="" />
const value = inputRef.current.value;DOM зберігає значення, а React читає його за потреби.
Підходить, якщо:
потрібно лише отримати дані під час відправлення;
форма проста;
не потрібна реакція на кожну зміну;
потрібно зменшити кількість стану в компоненті.
У контрольованій формі можна зберігати всі значення в одному об’єкті:
import { useState } from "react";
export default function RegistrationForm() {
const [form, setForm] = useState({
username: "",
password: "",
});
function handleChange(event) {
const { name, value } = event.target;
setForm((previousForm) => ({
...previousForm,
[name]: value,
}));
}
function handleSubmit(event) {
event.preventDefault();
console.log(form);
}
return (
<form onSubmit={handleSubmit}>
<label>
Ім’я користувача:
<input
name="username"
value={form.username}
onChange={handleChange}
/>
</label>
<label>
Пароль:
<input
name="password"
type="password"
value={form.password}
onChange={handleChange}
/>
</label>
<button type="submit">Зареєструватися</button>
</form>
);
}Властивість name допомагає визначити, яке саме поле змінилося:
const { name, value } = event.target;Потім обчислювана властивість [name] оновлює потрібну властивість об’єкта.
Використовуйте контрольовані поля, коли форма має логіку під час введення:
перевірку довжини пароля;
перевірку формату email;
залежні поля;
динамічне блокування кнопки;
повідомлення про помилки;
форматування номера телефону.
Використовуйте неконтрольовані поля, коли форма проста і дані потрібні переважно під час відправлення:
одне або кілька звичайних полів;
немає миттєвої валідації;
не потрібно відображати значення в іншій частині інтерфейсу;
потрібно швидко отримати значення через ref.
У більшості React-форм контрольований підхід є зручнішим для подальшого розширення, оскільки стан форми явно доступний компоненту.
value без onChange<input value={name} />Таке поле фактично стає доступним лише для читання. React встановлює значення, але користувач не може змінити його через інтерфейс.
Правильний варіант:
<input
value={name}
onChange={(event) => setName(event.target.value)}
/>value і defaultValueНе варто одночасно використовувати для одного поля value і defaultValue:
<input value={name} defaultValue="Іван" />Оберіть один підхід:
value разом з onChange — контрольоване поле;
defaultValue разом з ref — неконтрольоване поле.
ref.current під час першого відображенняДо монтування елемента значення ref.current може бути null. Читати значення поля потрібно в обробнику події або після того, як елемент уже створено.
function handleSubmit(event) {
event.preventDefault();
const value = inputRef.current.value;
}event.preventDefault()За замовчуванням браузер перезавантажує сторінку після відправлення форми. У React зазвичай потрібно скасувати цю поведінку:
function handleSubmit(event) {
event.preventDefault();
}Не змінюйте об’єкт стану безпосередньо:
form.username = "new-user";Створюйте новий об’єкт через функцію оновлення:
setForm((previousForm) => ({
...previousForm,
username: "new-user",
}));Контрольоване поле отримує значення зі стану React через value.
Зміни контрольованого поля обробляються через onChange.
Неконтрольоване поле зберігає значення в DOM.
Для доступу до неконтрольованого поля використовують useRef.
Для початкового значення неконтрольованого поля використовують defaultValue.
Контрольований підхід зручний для валідації та динамічної поведінки.
Неконтрольований підхід підходить для простих форм, де значення читають під час відправлення.
Не слід змішувати value і defaultValue в одному полі.