Хабрахабр

[Из песочницы] Формы должны быть простыми и декларативными

Когда я выбирал подходящую мне, разные библиотеки казались идеальными НО: форма на конфигах или колбеки в onSubmit эвенте, или асинхронный submit. Многие вставали перед выбором той или иной библиотеки для работы с формами в ReactJS. Если эти вопросы приходили вам в голову, или вы любите формы, приглашаю к прочтению статьи.
Почему формы для реакта не соответствуют принципам реакта, почему они выглядят как что-то особенное?

Давайте представим формы какими они должны быть.

Форма в реакте должна:

  • предоставлять управляемость полей и событий
  • максимально соответствовать html проекции
  • соблюдать декларативность и композицию
  • использовать типичные методы работы с React компонентами
  • иметь предсказуемое поведение

Форма в реакте не должна:

  • задавать модель управления
  • иметь избыточное состояние или требовать дополнительные данные
  • требовать настройку или обязательное использование функций-хелперов

Теперь попробуем описать идеальную форму опираясь на эти правила:

<Form action="/" method="post"> <Validation> <Field type="text" name="firstName"> <Field type="text" name="lastName"> <Transform> <Field type="number" name="minSalary"> <Field type="number" name="maxSalary"> <Transform> <Field type="email" name="email"> </Validation> <button type="submit">Send<button>
</Form>

Выглядит практически как обычная html форма, за исключением Field вместо input и неизвестных Validation и Transform. Вы, наверное, уже догадались что тег Validation должен проверять значение полей и создавать сообщения ошибки для них. Тег Transform в свою очередь необходим для вычисления полей minSalary и maxSalary.

Я что-то говорил про React?

Перенесёмся в реалии реакта и опишем ту же форму:

class MySexyForm extends React.Component }; this.validator = (model, meta) => { let errors = { ...meta.submitErrors }; if(model.firstName && model.firstName.length > 2) { errors = { firstName: ["First name length must be at minimum 2"] }; } if(model.lastName && model.lastName.length > 2) { errors = { ...errors, lastName: ["Last name length must be at minimum 2"] }; } return errors; }; this.transformer = (field, value, model) => { switch (field) { case "minSalary": if (parseInt(value) > parseInt(model.maxSalary)) { return { maxSalary: value }; } case "maxSalary": if (parseInt(value) < parseInt(model.minSalary)) { return { minSalary: value }; } } return {}; }; } onSubmit = (event) => (model) => { event.preventDefault(); console.log("Form submitting:", model); this.props.sendSexyForm(model); // абстрактный action после выполнения которого в форму приходят ошибки сабмита в виде submitErrors пропа } onModelChange = (model) => { console.log("Model was updated: ", model); this.setState({ model }); } render() { return ( <Form action="/" method="post" onSubmit={this.onSubmit} onModelChange={this.onModelChange} values={this.state.model} initValues={this.props.initValues} > <Validation validator={this.validator} submitErrors={this.props.submitErrors}> <Field type="text" name="firstName"> <Field type="text" name="lastName"> <Transform transformer={this.transformer}> <Field type="number" name="minSalary"> <Field type="number" name="maxSalary"> <Transform> <Field type="email" name="email"> </Validation> <button type="submit">Send<button> </Form> ); }
};

А так же сообщения об ошибках для данного поля. Я не стану подробно рассматривать Field компонент, представим что он рендерит input с переданными в Field пропами и дополнительными value и onChange.

Стоит объяснить появление новых полей initValues, values, onModelChange, onSubmit, validator, transformer.

Начнём с пропов добавленных в Form.

Эвент хендлер onSubmit позволяет перехватить эвент сабмита формы получить доступ к этому эвенту и к текущим значениям полей формы через model.

Эвент хендлер onModelChange позволяет отследить изменения в полях формы.
С помощью values мы можем управлять значениями полей, а initValues позволяет задать начальные значения.

Этот базовый функционал предоставляет большинство библиотек для работы с формами в реакте, ничего необычного, всё так как должно быть.

Рассмотрим тег Validation, у него появились два пропа

  1. validator — функция возвращающая ошибки валидации на основе переданных значений полей формы
  2. submitErrors — дополнительное rest поле передающееся вторым аргументом в функцию валидатор, в нём мы передаём полученные с сервера ошибки после сабмита

К сожалению, я не встречал подобной или похожей реализации валидации, хотя она кажется очевидной: у нас есть функция валидации которая получает данные и возвращает на их основе ошибки, никакой side effect логики, всё так, как должно быть в реакте.

Он перехватывает изменения у вложенных полей и вызывает функцию — transformer, принимающую три аргумента: Перейдём к компоненту Transform.

  • field — имя поля в котором произошло изменение
  • value — новое значение этого поля
  • model — текущее значение полей формы с предыдущим значением изменившегося поля

Она должна вернуть объект вида { [field]: value } который будет использован для обновление других полей формы.

Тоже очевидная реализация вычисляемых полей.

И… что мы имеем в итоге?

Так как изначально мы использовали декларативный подход и композицию мы можем комбинировать компоненты валидации и трансформации и использовать их для отдельных групп полей.

В компоненте Form отсутствуют лишние пропы отвечающие за дополнительный функционал (трансформация и валидация).

Сама форма имеет вид схожий с html проекцией, что упрощает понимание. Field получает своё значение, информацию об ошибках и колбек функции по средствам контекстов, что позволяет создавать дополнительные компоненты для работы с формой и делегировать ответственность.

react-painlessform

Я написал собственную библиотеку, которая помогает делать формы просто и понятно. С кодом можно ознакомиться на Github.

А так же посмотреть живой пример из статьи.

Спасибо за внимание

Раз вы дочитали до конца, то вы сильно любите формы или статья была интересна, я буду рад прочитать комментарии и услышать ваше мнение по поводу форм в реакте.

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»