# Обработка форм, React-Final-Form
## Сбор данных из формы
Рассмотрим HTML- форму:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
</head>
<body>
<div class="container">
<form action="" method="GET" class="form" novalidate>
<div class="form-group">
<label for="email">Введите ваш E-mail</label>
<input type="email" class="form-control" id="email" name="email" required placeholder="E-mail">
</div>
<div class="form-group">
<label for="name">Введите ваше имя</label>
<input type="text" class="form-control" id="name" name="name" type="text" placeholder="Имя">
</div>
<div class="form-group">
<label><input type="checkbox" class="checkbox" name="confirm" id="confirm">Согласен купить квартиру для Владимира Языкова</label>
</div>
<div class="form-group">
<button class="btn btn-success">Прощай, деньги!</button>
</div>
</form>
</div>
<script src="once2.js"></script>
</body>
</html>
```
### Сбор и отправка данных
Для сбора данных при отправке формы будет работать следующий код:
```javascript
document.addEventListener('submit', e => {
const target = e.target.closest('.form');
if (!target) {
return
}
e.preventDefault();
const elements = Array.from(target.elements);
const data = elements.filter(({ name }) => name)
.reduce((result, item) => {
const { value, name, type, checked } = item;
const isTextType = !['checkbox', 'radio'].includes(type);
if (isTextType || (!isTextType && checked)) {
result[name] = value;
}
return result;
}, {})
console.log(data);
});
```
Более удобный вариант сбора - использование обектов FormData и URLSearchParams. При приведении объекта к строке получается
строка с параметрами, разделёнными & и =:
```javascript
const formData = new FormData(target);
const data = Object.fromEntries(formData);
const params = new URLSearchParams(formData);
const url = `https://google.com?${params}`;
```
URLSearchParams можно использовать и для отправки POST запросов в fetch:
```javascript
const formData = new FormData(target);
const data = Object.fromEntries(formData);
const params = new URLSearchParams(formData);
fetch('https://google.com/?', {
method: 'POST',
body: params
});
```
Часто необходимо отправлять JSON-данные. В таком случае используется JSON.stringify:
```javascript
const formData = new FormData(target);
const data = Object.fromEntries(formData);
const params = new URLSearchParams(formData);
fetch('https://google.com/?', {
method: 'POST',
body: JSON.stringify(data)
});
```
Объект FormData можно отправлять напрямую в свойство body функции fetch или в метод send XMLHttpRequest
### Валидация
В HTML5 есть естественный механизм проверки правильности формы
При наличии:
- обязательных полей (атрибут required)
- Указании типах полей или уточнении валидации (например, с помощью атрибутов minlength, maxlength, min, max и пр.)
в случае ошибки ввода, форма не отправится и не возникнет её событие submit
Данный механизм можно отключить, используя атрибут novalidate:
```html
<form action="" method="GET" class="form" novalidate>
```
Для проверки правильности заполнения можно обратиться к [Constraint validation API](https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation). У формы есть методы:
- checkValidity - проверяет правильность введенных данных. Возвращает true, если все поля заполнены верно
- reportValidity - выводит подсказку в случае, если в одном из полей формы допущена ошибка. Возвращает результат работы checkValidity()
Аналогично, данные методы есть у каждого поля формы. Помимо этого они имеют объект validity, который позволяет уточнить причину ошибки и проверить, правильно ли заполнено конкретно данное поле:
```javascript
document.addEventListener('submit', e => {
const target = e.target.closest('.form');
if (!target) {
return
}
e.preventDefault();
const isValidForm = target.checkValidity();
const field = [...target.elements].find(element => !element.validity.valid);
console.log({ field, isValidForm });
});
```
### Возможность повторной отправки
Чтобы форма не отправлялась дважды, необходимо заблокировать данный механизм до получения результата
первой отправки:
```javascript
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const sendData = async data => {
const id = Math.random().toString(36).slice(2);
console.log(`${id}: Отравляю данные`);
await delay(2000);
console.log(`${id}: Данные отправлены!`);
}
const setElements = value =>
form => [...form.elements]
.forEach(element => element.disabled = value);
const disableElements = setElements(true);
const enableElements = setElements(false);
document.addEventListener('submit', async e => {
const target = e.target.closest('.form');
if (!target) {
return
}
e.preventDefault();
const formData = new FormData(target);
const data = Object.fromEntries(formData);
disableElements(target);
await sendData(data);
enableElements(target);
});
```
Альтернативная работа механизма на классах:
```javascript
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const sendData = async data => {
const id = Math.random().toString(36).slice(2);
console.log(`${id}: Отравляю данные`);
await delay(2000);
console.log(`${id}: Данные отправлены!`);
}
document.addEventListener('submit', async e => {
const target = e.target.closest('.form');
if (!target) {
return
}
e.preventDefault();
if (target.classList.contains('form_disabled')) {
return;
}
const formData = new FormData(target);
const data = Object.fromEntries(formData);
target.classList.add('form_disabled');
await sendData(data);
target.classList.remove('form_disabled');
});
```
## Недостатки встроенного механизма валидации
- Недостаточно гибкости
- Неинформативные подсказки
- Большое число ограничений при попытке стилизации
## Основные преимущества React-Final-Form;
- Удобная валидация формы
- Исключает повторную отправку
## Устанавливаем React-Final-Form
npm
```bash
npm install --save final-form react-final-form
```
yarn
```sh
yarn add final-form react-final-form
```
## Создаем формы с помощью React-Final-Form
App.js:
```jsx
import 'bootstrap/dist/css/bootstrap.min.css';
import { SubscribeForm } from './components';
function App() {
return (
<div className="App">
<SubscribeForm />
</div>
);
}
export default App;
```
SubscribeForm
```jsx
import Container from 'react-bootstrap/Container';
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
import { Form as FinalForm, Field } from 'react-final-form'
export const SubscribeForm = () => {
const onSubmit = data => {
console.log(data);
};
return (
<Container>
<FinalForm
onSubmit={onSubmit}
render={({ handleSubmit }) => (
<Form noValidate={true} onSubmit={handleSubmit}>
<Field name="email" type="email">
{({ input, meta }) => (
<Form.Group controlId="email">
<Form.Label>Введите ваш E-mail</Form.Label>
<Form.Control {...input} required placeholder="E-mail" />
</Form.Group>
)}
</Field>
<Field name="name" required>
{({ input, meta }) => (
<Form.Group controlId="name">
<Form.Label>Введите ваше Имя</Form.Label>
<Form.Control {...input} placeholder="Имя"/>
</Form.Group>
)}
</Field>
<Field name="confirm" type="checkbox" >
{({ input, meta }) => (
<Form.Group controlId="confirm">
<Form.Check {...input} label="Согласен купить квартиру для Владимира Языкова"/>
</Form.Group>
)}
</Field>
<Button variant="success" type="submit">
Отдать мои деньги!
</Button>
</Form>
)}/>
</Container>
);
};
```
## onSubmit
prop onSubmit - обработчик события, управляющего формой. Таким образом мы обёртываем обычный компонент <form/> на <Form/>
```jsx
const onSubmit = data => {
console.log(data);
};
return (
<Container>
<FinalForm
onSubmit={onSubmit}
```
Теперь в объекте data будут содержаться все данные полей формы
## Основные компоненты
1. Form - обёртывает привычную форму
2. Field - заменяет компонент input. Может быть переопределён
## Собственное поле ввода
Для отображения своего поля ввода необходимо:
1. Указать его имя в параметре name компонента Field
2. Передать в качестве дочернего узла js-функцию, возвращающую необходимый компонент/разметку
```jsx
<Field name="name" required>
{({ input, meta }) => (
<Form.Group controlId="name">
<Form.Label>Введите ваше Имя</Form.Label>
<Form.Control {...input} placeholder="Имя"/>
</Form.Group>
)}
</Field>
```
## Валидация
Осуществляется с помощью prop validate либо формы, либо поля:
```js
const required = value => (value ? undefined : 'Required')
```
```jsx
<Field name="firstName" validate={required}> ...
```
Функция должна вернуть строку в случае ошибки, иначе undefined
Валидация формы может выглядеть следующим образом
```jsx
<Form
onSubmit={onSubmit}
validate={values => {
const errors = {}
if (!values.username) {
errors.username = 'Required'
}
if (!values.password) {
errors.password = 'Required'
}
if (!values.confirm) {
errors.confirm = 'Required'
} else if (values.confirm !== values.password) {
errors.confirm = 'Must match'
}
return errors
}}
```
## Какие основные задачи форм?
- Сбор данных из полей
- Проверка правильности заполнения, валидация
- Асинхронная отправка
- Отправка данных только один раз
## Достаточно ли проверки данных только на клиенте?
frontend даёт удобный интерфейс для посетителей. Для обеспечения безопасности все проверки должны обязательно быть на backend
## Преимущества библиотек при работе с формами
- Управление состоянием
- Управление перерисовкой