# Обработка форм, 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 ## Преимущества библиотек при работе с формами - Управление состоянием - Управление перерисовкой