Формы и общее состояние
Управляемые поля
В обычном HTML поле ввода само хранит то, что в него напечатали. В React принято, чтобы значение поля хранилось в состоянии, а поле только показывало его. Такое поле называют управляемым (controlled):
function Search() {
const [query, setQuery] = useState("");
return (
<>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<p>Ищем: {query}</p>
</>
);
}
Круг замыкается так: пользователь печатает → onChange → setQuery(новый текст) → React
перерисовывает → поле показывает query. В React onChange срабатывает на каждый символ (как
событие input в DOM).
Зачем так сложно? Значение всегда под рукой в переменной: его можно проверить, преобразовать
(например, setQuery(e.target.value.toUpperCase())), очистить (setQuery("")) или показать в другом
месте страницы.
Если написать value={...} без onChange, поле станет «только для чтения» — React будет
возвращать ему значение из состояния, и печатать не получится.
Разные виды полей
<textarea value={text} onChange={(e) => setText(e.target.value)} />
<select value={city} onChange={(e) => setCity(e.target.value)}>
<option value="msk">Москва</option>
<option value="dyu">Душанбе</option>
</select>
<input type="checkbox" checked={agree} onChange={(e) => setAgree(e.target.checked)} />
У чекбокса вместо value — checked и e.target.checked.
Отправка формы
function Subscribe({ onSubscribe }) {
const [email, setEmail] = useState("");
const handleSubmit = (e) => {
e.preventDefault(); // без перезагрузки страницы
onSubscribe(email);
setEmail(""); // очистить поле
};
return (
<form onSubmit={handleSubmit}>
<input value={email} onChange={(e) => setEmail(e.target.value)} />
<button>Подписаться</button>
</form>
);
}
Кнопка внутри формы по умолчанию отправляет её, и сработает onSubmit — как по клику, так и по Enter.
Проверка без лишнего состояния
Можно ли отправить форму, не нужно хранить в отдельном useState — это вычисляется из того, что
уже есть, прямо при отрисовке:
const valid = email.includes("@") && password.length >= 6;
<button disabled={!valid}>Войти</button>
Правило: если значение можно посчитать из props и состояния — считай, а не храни. Лишнее состояние легко «рассинхронизировать» с основным.
Подъём состояния
Иногда двум компонентам нужно одно и то же значение: фильтр и список, два поля конвертера, товар и корзина. Тогда состояние поднимают в их общего родителя, а детям передают значение и функцию для изменения через props:
function Filter({ value, onChange }) {
return <input value={value} onChange={(e) => onChange(e.target.value)} />;
}
function List({ items, query }) {
return (
<ul>
{items.filter((i) => i.includes(query)).map((i) => <li key={i}>{i}</li>)}
</ul>
);
}
function App() {
const [query, setQuery] = useState(""); // состояние у общего родителя
return (
<>
<Filter value={query} onChange={setQuery} />
<List items={["кот", "кит", "пёс"]} query={query} />
</>
);
}
Данные в React текут сверху вниз (через props), а события — снизу вверх (через функции в props). У каждого значения один владелец — так приложение остаётся предсказуемым.
Итог
- Управляемое поле:
value={состояние}+onChange={(e) => set(e.target.value)}; чекбокс —checked. - Форма:
onSubmit+e.preventDefault(). - Что можно вычислить — вычисляй при отрисовке, а не храни в состоянии.
- Общие данные — в общем родителе; детям — значение и функция-обработчик через props.