Работа с формами в React, как известно, сложна, особенно когда задействованы динамические поля. Существует ряд библиотек, которые упрощают весь процесс. Одна из таких библиотек - React Hook Form. Вместо набора компонентов формы React Hook Form, как следует из названия, предоставляет различные хуки, которые помогают контролировать поведение формы, оставляя детали реализации отдельных компонентов пользователю. Этот подход имеет несколько преимуществ, в основном то, что пользователи не привязаны к какой-либо конкретной структуре пользовательского интерфейса или предопределенным компонентам формы.

В этом посте мы создадим простую форму рецепта, которая позволяет вводить основные детали вместе с динамическим списком ингредиентов. Окончательный результат будет выглядеть так:

С точки зрения пользовательского интерфейса это не выглядит слишком модным, поскольку основное внимание уделяется использованию React Hook Form. Помимо этого, мы будем использовать Semantic UI React, библиотеку компонентов пользовательского интерфейса, и Emotion / styled, чтобы иметь возможность настраивать стили этих компонентов.

В качестве первого шага давайте установим все необходимые зависимости:

npm i @emotion/core @emotion/styled semantic-ui-react semantic-ui-css react-hook-form

Теперь мы можем настроить наш компонент формы в новый файл с именем Form.js.

Кроме того, не забудьте добавить import "semantic-ui-css/semantic.min.css"; в index.js над пользовательскими стилями index.css.

Форма База

Разобравшись со всей этой настройкой, мы, наконец, можем начать работу над самой формой. Начнем с раздела Основы, в котором будет общая информация о рецепте. Чтобы упростить группировку полей формы в разделы, давайте добавим настраиваемый компонент под названием FieldSet, который представляет собой небольшую абстракцию поверх исходного HTML fieldset.

Для самой формы мы будем использовать компонент Form из Semantic UI React, который также имеет несколько удобных подкомпонентов, таких как Form.Field. Для этой простой формы рецепта у нас будет только несколько основных полей, таких как название рецепта, описание и количество порций. Добавим их в форму.

Здесь мы добавляем поля рецептов с их метками, в результате получается простая форма ниже. Обратите внимание на использование атрибутов name в элементах формы, так как они немного пригодятся. Также мы используем комбинацию атрибутов htmlFor и id для улучшения доступности полей.

Пришло время использовать React Hook Form для управления состоянием нашей формы. Одним из преимуществ библиотеки является то, что она упрощает управление состоянием без необходимости добавлять кучу setState ловушек. Все, что нам нужно сделать, это использовать комбинацию атрибутов name и ref для регистрации полей в состоянии формы.

Начнем с импорта и вызова хука useForm, который возвращает несколько полезных помощников. В этом случае мы используем register для присвоения поля формы через его имя соответствующему свойству в состоянии. Вот почему здесь важно добавлять имена в поля. Нам также нужно обернуть нашу функцию отправки в handleSubmit обратный вызов. Теперь, если мы введем детали рецепта в поля формы и нажмем Save, мы должны увидеть в консоли следующий объект:

{
  name: "Pancakes",
  description: "Super delicious pancake recipe",
  amount: "10" 
}

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

Проверка формы и обработка ошибок

Значение register, которое мы получаем от useForm, на самом деле является функцией, которая принимает параметры проверки как объект. Доступно несколько правил проверки:

  • требуется
  • мин
  • Максимум
  • minLength
  • максимальная длина
  • шаблон
  • подтверждать

Чтобы сделать имя рецепта обязательным полем, все, что нам нужно сделать, - это вызвать регистр с опорой required:

Кроме того, useForm возвращает объект errors, который сопоставляет все возникшие ошибки с именами полей. Таким образом, в случае отсутствия имени рецепта errors будет иметь объект name с типом required. Также стоит отметить, что вместо указания правила проверки с логическим значением мы также можем передать ему строку, которая будет использоваться в качестве сообщения об ошибке:

В качестве альтернативы для этого можно использовать свойство message. Сообщение об ошибке можно будет просмотреть позже через errors.name.message. Мы также передаем ошибки поля как логические значения в Form.Field, чтобы переключить состояние ошибки.

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

Если мы попытаемся отправить форму с недопустимыми данными, мы получим удобные сообщения проверки для полей.

К полям также можно применить пользовательские правила проверки с помощью правила validate. Это может быть функция или объект функций с разными правилами проверки. Например, мы можем проверить, равно ли значение поля так:

Обработка ввода чисел

В текущей форме мы используем числовое поле для ввода порций. Однако из-за того, как работают элементы ввода HTML, при отправке формы это значение будет строкой в ​​данных формы. В некоторых случаях это может быть не то, что нам нужно, например. если ожидается, что данные будут числом на бэкэнде. Одним из простых способов решения этой проблемы было бы преобразование суммы в число при отправке, однако это не оптимально, особенно в случаях, когда у нас много таких полей. Лучшим решением было бы выделить ввод числа в отдельный компонент с логикой преобразования типа. Таким образом, при отправке формы данные будут иметь нужные нам типы. Чтобы подключить этот компонент к форме, React Hook Form предоставляет Controller - оболочку для работы с управляемыми внешними компонентами.

Сначала создадим такой компонент с именем NumberInput.

После этого мы можем заменить текущее поле amount этим новым компонентом.

Вместо register мы используем объект control, полученный от useForm, для проверки мы используем rules prop. Нам все еще нужно добавить атрибут name к Controller, чтобы зарегистрировать его. Затем мы передаем компонент ввода через render prop. Теперь данные для рецептурных порций будут сохраняться в форме, как и раньше, при использовании внешнего компонента.

Динамические поля

Ни один рецепт не обходится без ингредиентов. Однако мы не можем добавить в нашу форму поля с фиксированными ингредиентами, поскольку их количество зависит от рецепта. Обычно нам нужно использовать собственную логику для обработки динамических полей, однако React Hook Form поставляется с настраиваемым хуком для работы с динамическими входами - useFieldArray. Он принимает объект управления формы и имя поля, возвращая несколько утилит для работы с динамическими входами. Давайте посмотрим, как это работает, добавив поля ингредиентов в форму рецепта.

Первый шаг - импортировать useFieldArray и вызвать его с помощью control, полученного из ловушки формы, а также передать ему имя поля. useFieldArray возвращает несколько утилит для управления динамическими полями, из которых мы будем использовать append, remove и массив самих полей. Полный список функций утилиты доступен на сайте документации библиотеки. Поскольку у нас нет значений по умолчанию для ингредиентов, поле изначально пустое. Мы можем начать заполнять его с помощью функции append и предоставления ей значений по умолчанию для пустых полей. Обратите внимание, что отображение полей выполняется по их индексу в массиве, поэтому важно иметь имена полей в формате fieldArrayName[fieldIndex][fieldName]. Мы также можем удалить поля, передав индекс поля функции delete. Теперь, после добавления нескольких полей ингредиентов и заполнения их значений, когда мы отправляем форму, все эти значения будут сохранены в поле ingredients формы.

Это практически все, что нужно для создания полностью функциональной и легко управляемой формы с помощью React Hook Form. В библиотеке есть много других функций, не описанных в этом посте, поэтому не забудьте проверить документацию, чтобы найти больше примеров.

Первоначально опубликовано на https://claritydev.net.