Основная цель любой формы - получить данные пользователя. Элементы формы это контроллируемые компоненты, значения всех полей должны быть в state.
Паттерн работы с элементами форм очень простой.
Поле в state определяет значение атрибута value инпута
Событию onChange передается метод изменяющий поле в состоянии
Получаем замкнутый круг.
При событии onChange, метод класса обновляет поле в состоянии
При изменении состояния происходит ре-рендер
Инпут отображается с обновленными данными
class App extends Component {
state = {
value: '',
};
handleChange = e => {
this.setState({ value: e.target.value });
};
render() {
const { value } = this.state;
return <input type="text" value={value} onChange={this.handleChange} />;
}
}
Получается что не интерфейс определяет какие у нас данные, а наоборот, данные определяют то, что видит пользователь, обновляя DOM при изменении состояния компонента.
2. Формы
Создадим форму регистрации.
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.login);
};
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>
);
}
}
Добавим поля для email и password, а заодно используем очень полезный паттерн для callback-функции передаваемой в onChange.
/*
* Выносим объект с примитивами в константу чтобы было удобно сбрасывать.
* Нельзя использовать если в каком-то свойстве состояния хранится сложный тип.
*/
const INITIAL_STATE = {
login: '',
email: '',
password: '',
};
class SignUpForm extends React.Component {
state = { ...INITIAL_STATE };
/*
* Для всех инпутов создаем один обарботчик
* Различать инпуты будем по атрибуту name
*/
handleChange = ({ target }) => {
const { name, value } = 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>
);
}
}
3. Генерация Id элементов формы
Доступность (accessibility, a11y) - очень важная тема в современном вебе. HTML-атрибут for тега label помогает читалкам и другим вспомогательным инструментам. В React он представлен jsx-атрибутом htmlFor.
Для генерации уникальных идентификаторов элементов форм используется следующий подход: для каждого экземляра компонента, при его инициализации, создается набор уникальных идентификаторов, ханящихся на экземпляре. Таким образом, между разными формами получаем уникальные id.
// Можно использовать любой пакет для генерации уникальных строк
import shortid from 'shortid';
class Form extends React.Component {
loginInputId = shortid.generate();
render() {
return (
<form>
<label htmlFor={this.loginInputId}>Login</label>
<input type="text" name="login" id={this.loginInputId} />
</form>
);
}
}
4. Чекбоксы
Работа с чекбоксами проста и понятна. Чекбокс может быть всего в 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 = ({ target }) => {
const { name, value, type, checked } = 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>
);
}
}
5. Радиокнопки
В отличии от привычной группировки по значению атрибута name, в React радиокнопка это всего лишь элемент интерфейса, который:
Знает выбран ли он
Может попросить форму изменить выделение
Обычно у радиокнопок есть и атрибут checked и value. К примеру радио кнопка отвечающая за выбор пола пользователя.