Форми
Неконтрольовані елементи
Основна мета будь-якої форми – отримати дані користувача. Для цього під час
сабміту можна отримати значення полів з її властивості elements
або,
використовуючи FormData
. Такий прийом доречно використовувати, коли дані полів
форми потрібні тільки під час її сабміту.
class LoginForm extends Component {
handleSubmit = evt => {
evt.preventDefault();
const form = evt.currentTarget;
const login = form.elements.login.value;
const password = form.elements.password.value;
console.log(login, password);
this.props.onSubmit({ login, password });
form.reset();
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<input type="text" name="login" />
<input type="password" name="password" />
<button type="submit">Login</button>
</form>
);
}
}
ReactDOM.render(
<LoginForm onSubmit={values => console.log(values)} />,
document.getElementById("root")
);
Контрольовані елементи
Якщо значення елементів форм необхідно отримати в момент зміни поля і зробити
щось динамічно, вони повинні бути контрольовані. Тобто значення всіх полів
повинні бути в state
. Цей прийом роботи з елементами форм – досить простий.

- Поле в
state
визначає значення атрибутуvalue
поля - Події
onChange
передається метод, що змінює поле в стані
Отримуємо замкнене коло.
- Під час події
onChange
метод класу оновлює поле в стані - Під час зміни стану відбувається ререндер
- Інпут відображається з оновленими даними
Недолік у тому, що вся форма буде повторно рендеритися під час кожної зміни будь-якого поля, але для невеликих форм – це не проблема.
class App extends Component {
state = {
inputValue: "",
};
handleChange = evt => {
this.setState({ inputValue: evt.target.value });
};
render() {
const { inputValue } = this.state;
return (
<input type="text" value={inputValue} onChange={this.handleChange} />
);
}
}
Виходить, що не інтерфейс визначає, які у нас дані, а навпаки, – дані визначають те, що бачить користувач, оновлюючи DOM під час зміни стану компонента.
Складні форми
Створимо форму реєстрації.
class SignUpForm extends Component {
state = {
login: "",
};
// Відповідає за оновлення стану
handleChange = e => {
this.setState({ login: e.target.value });
};
// Викликається під час відправлення форми
handleSubmit = evt => {
evt.preventDefault();
console.log(`Signed up as: ${this.state.login}`);
// Проп, який передається формі для виклику під час сабміту
this.props.onSubmit({ ...this.state });
};
render() {
const { login } = this.state;
return (
<form onSubmit={this.handleSubmit}>
<label>
Name
<input
type="text"
placeholder="Enter login"
value={login}
onChange={this.handleChange}
/>
</label>
<button type="submit">Sign up as {login}</button>
</form>
);
}
}
ReactDOM.render(
<SignUpForm onSubmit={values => console.log(values)} />,
document.getElementById("root")
);
Додамо поля для email
і password
, а заразом використаємо дуже корисний
патерн для callback-функції, що передається в onChange
.
// Виносимо об'єкт із примітивами в константу, щоб було зручно скидати.
// Не можна використовувати, якщо в якійсь властивості стану зберігається складний тип.
const INITIAL_STATE = {
login: "",
email: "",
password: "",
};
class SignUpForm extends React.Component {
state = { ...INITIAL_STATE };
// Для всіх інпутів створюємо один обробник
// Розрізняти інпути будемо за атрибутом name
handleChange = evt => {
const { name, value } = evt.target;
this.setState({ [name]: value });
};
handleSubmit = evt => {
evt.preventDefault();
const { login, email, password } = this.state;
console.log(`Login: ${login}, Email: ${email}, Password: ${password}`);
this.props.onSubmit({ ...this.state });
this.reset();
};
reset = () => {
this.setState({ ...INITIAL_STATE });
};
render() {
const { login, email, password } = this.state;
return (
<form onSubmit={this.handleSubmit}>
<label>
Name
<input
type="text"
placeholder="Enter login"
name="login"
value={login}
onChange={this.handleChange}
/>
</label>
<label>
Email
<input
type="email"
placeholder="Enter email"
name="email"
value={email}
onChange={this.handleChange}
/>
</label>
<label>
Password
<input
type="password"
placeholder="Enter password"
name="password"
value={password}
onChange={this.handleChange}
/>
</label>
<button type="submit">Sign up as {login}</button>
</form>
);
}
}
Генерація Id елементів форми
Доступність (accessibility, a11y) – дуже важлива тема в сучасному вебі.
HTML-атрибут for
тегу label
допомагає асистивним технологіям та іншим
допоміжним інструментам. В React він представлений jsx-атрибутом htmlFor
.
Для генерації унікальних ідентифікаторів елементів форм використовується наступний підхід: для кожного екземпляра компонента, під час його ініціалізації, створюється набір унікальних ідентифікаторів, що зберігаються на екземплярі. Таким чином, між різними формами одержуємо унікальні id.
// Можна використовувати будь-який пакет для генерації унікальних рядків
import { nanoid } from "nanoid";
class Form extends React.Component {
loginInputId = nanoid();
render() {
return (
<form>
<label htmlFor={this.loginInputId}>Login</label>
<input type="text" name="login" id={this.loginInputId} />
</form>
);
}
}
Чекбокси
Робота з чекбоксами – проста та зрозуміла. Чекбокс може бути всього у 2-х
станах: true
або false
.
Особливості чекбоксів:
- Ім'я атрибута, якому передається поточне значення зі
state
. Для чекбоксів – цеchecked
, і передаємо туди буль - Під час обробки події
onChange
, для отримання значення, в об'єкті події звертаємось до властивостіevent.target.checked
- Якщо чекбокс повинен зберігати значення, його можна повісити на атрибут
value
і прочитати з об'єкта події
Додамо до нашої форми реєстрації чекбокс для підтвердження згоди користувача, і зробимо кнопку сабміту неактивною, доки неактивний чекбокс.
const INITIAL_STATE = {
login: "",
email: "",
password: "",
agreed: false,
};
class SignUpForm extends React.Component {
state = {
...INITIAL_STATE,
};
handleChange = evt => {
const { name, value, type, checked } = evt.target;
// Якщо тип елемента – checkbox, беремо значення checked,
// в іншому випадку – value
this.setState({ [name]: type === "checkbox" ? checked : value });
};
handleSubmit = e => {
e.preventDefault();
const { login, email, password, agreed } = this.state;
console.log(
`Login: ${login}, Email: ${email}, Password: ${password}, Agreed: ${agreed}`
);
/* ... */
};
render() {
const { login, email, password, agreed } = this.state;
return (
<form onSubmit={this.handleSubmit}>
{/* ... */}
<label>
Agree to terms
<input
type="checkbox"
checked={agreed}
onChange={this.handleChange}
/>
</label>
<button type="submit" disabled={!agreed}>
Sign up as {login}
</button>
</form>
);
}
}
Радіокнопки
На відміну від звичного групування за значенням атрибуту name
, в React
радіокнопка – це лише елемент інтерфейсу, який:
- Знає, чи він вибраний
- Може попросити форму змінити виділення
Зазвичай у радіокнопок є і атрибут checked
і value
. Наприклад, радіокнопка,
що відповідає за вибір статі користувача.
<input
type="radio"
checked={this.state.gender === "male"}
value="male"
onChage={this.handleGenderChage}
/>
Додамо групу радіокнопок у нашу форму.
// Використовуємо Enumerable, щоб не створювати антипатерн "магічні рядки"
const Gender = {
MALE: "male",
FEMALE: "female",
};
const INITIAL_STATE = {
login: "",
email: "",
password: "",
agreed: false,
gender: null,
};
class SignUpForm extends React.Component {
state = {
...INITIAL_STATE,
};
/*... */
render() {
const { login, email, password, agreed, gender } = this.state;
return (
<form onSubmit={this.handleSubmit}>
{/* ... */}
<section>
<h2>Choose your gender</h2>
<label>
Male
<input
type="radio"
checked={gender === Gender.MALE}
name="gender"
value={Gender.MALE}
onChange={this.handleChange}
/>
</label>
<label>
Female
<input
type="radio"
checked={gender === Gender.FEMALE}
name="gender"
value={Gender.FEMALE}
onChange={this.handleChange}
/>
</label>
</section>
<button type="submit" disabled={!agreed}>
Sign up as {login}
</button>
</form>
);
}
}
Селект
Все просто – є селект, є опції, у селекта є value
та onChange
. Додаємо вибір
вікової категорії.
const INITIAL_STATE = {
login: "",
email: "",
password: "",
agreed: false,
gender: null,
age: "",
};
class SignUpForm extends React.Component {
state = {
...INITIAL_STATE,
};
/* ... */
render() {
const { login, email, password, agreed, gender, age } = this.state;
return (
<form onSubmit={this.handleSubmit}>
{/* ... */}
<label>
Choose your age
<select name="age" value={age} onChange={this.handleChange}>
<option value="" disabled>
...
</option>
<option value="18-25">18-25</option>
<option value="26-35">26-35</option>
<option value="36+">36+</option>
</select>
</label>
<button type="submit" disabled={!agreed}>
Sign up as {login}
</button>
</form>
);
}
}