Формы и валидация в Ionic React

Ionic Framework обеспечивает первоклассную поддержку для создания быстрых и оптимизированных под мобильные устройства приложений для любой платформы с использованием React. Изучив это руководство, вы узнаете, как создавать формы при работе с Ionic React и как сделать эти формы интерактивными, добавив правила валидации с полезными текстовыми подсказками.

Ionic Framework — это набор инструментов пользовательского интерфейса, предназначенный для создания кроссплатформенных мобильных приложений с использованием HTML, CSS и JavaScript. Релиз Ionic 5, состоявшийся в начале 2020 года сопровождался официальной поддержкой React, что позволило React- разработчикам с легкостью создавать мобильные приложения с использованием привычных инструментов. Однако в нем не так хорошо поддерживается работа с формами, и многие из существующих библиотек, предназначенных для создания форм в экосистеме React, плохо работают с компонентами Ionic Framework.

В этом руководстве рассказывается о том, как создавать формы с использованием компонентов ввода пользовательского интерфейса Ionic React. Изучив его, вы также узнаете, как использовать данную библиотеку для обнаружения изменений ввода формы и реагирования на правила валидации. Наконец, вы научитесь делать формы доступными для программ чтения с экрана, добавляя полезный текст в атрибуты ARIA.

Компоненты формы Ionic

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

Ionic предоставляет готовые компоненты для работы с формами — некоторые из которых включают в себя IonItem, IonLabel, IonInput, IonCheckbox и IonRadio. Мы можем комбинировать эти компоненты для создания стандартных форм, не добавляя стили самостоятельно.

Например, приведенный ниже код:

<form className="ion-padding">
  <IonItem>
    <IonLabel position="floating">Username</IonLabel>
    <IonInput />
  </IonItem>
  <IonItem>
    <IonLabel position="floating">Password</IonLabel>
    <IonInput type="password" />
  </IonItem>
  <IonItem lines="none">
    <IonLabel>Remember me</IonLabel>
    <IonCheckbox defaultChecked={true} slot="start" />
  </IonItem>
  <IonButton className="ion-margin-top" type="submit" expand="block">
    Login
  </IonButton>
</form>

формирует форму входа в систему, которая выглядит следующим образом:

Стандартная форма входа на iOS

По умолчанию компоненты формы Ionic отлично смотрятся на iOS или Android, но они могут быть немного громоздкими, если вы работаете с React. Как и в случае с большинством инструментов в экосистеме React, вам нужно решить, как именно вы хотите создавать формы, когда речь идет о функциональности и доступности — и то, и другое не менее важно, чем дизайн.

Хотя на выбор уже доступно много хелперов форм React, большинство из них не работают с компонентами форм Ionic. Я подозреваю, что основная причина этого заключается в том, что событие срабатывает при изменении значения поля в Ionic onIonChange, тогда как большинство существующих библиотек форм прослушивают onChange.

Событие изменения срабатывает при изменении поля

React Hook Form: небольшая и быстрая библиотека форм React

К счастью, это не так уж и плохо. Недавно я наткнулся на React Hook Form (RHF), библиотеку для работы с формами в React-проектах. Она обеспечивает поддержку контролируемых или неконтролируемых компонентов и проверку ввода, а API основан на хуках, поэтому данная библиотека работает только с функциональными компонентами.

На мой взгляд, самая привлекательная функция для разработчиков, использующих Ionic React — это компонент <Controller /> — оболочка, которую он предоставляет для работы с управляемыми компонентами. Компонент имеет свойство onChangeName, которое можно использовать для указания имени события изменения для любого экземпляра компонента, который вы ему передаете. В следующих разделах я покажу вам, как именно это упрощает работу с формами в Ionic.

Создание формы регистрации

Давайте посмотрим, как RHF может помочь с функционалом формы, когда мы создаем в Ionic регистрационную форму. Если вы используете последнюю версию Ionic CLI (запустите npm i -g @ionic/cli для подтверждения), запустите новое приложение Ionic с React, выполнив приведенную ниже команду:

ionic start myApp blank --type=react

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

Примечание. Перед тем, как продолжить работу с этим руководством, вы должны удалить компонент ExploreContainer и его импорт в Home.tsx.

Чтобы начать работу с формой, установите пакет React Hook Form, выполнив приведенную ниже команду в корневом каталоге проекта:

yarn add react-hook-form

Это сделает библиотеку React Hook Form доступной в проекте. Создадим поле ввода формы с помощью этой библиотеки. Откройте файл Home.tsx и замените его содержимое следующим:

import { IonContent, IonPage, IonText, IonItem, IonLabel, IonInput, IonButton } from "@ionic/react";
import React from "react";
import "./Home.css";
import { Controller, useForm } from 'react-hook-form';

const Home: React.FC = () => {
  const { control, handleSubmit } = useForm();

  const registerUser = (data) => {
    console.log('creating a new user account with: ', data);
  }

  return (
    <IonPage>
      <IonContent className="ion-padding">
        <IonText color="muted">
          <h2>Create Account</h2>
        </IonText>
        <form onSubmit={handleSubmit(registerUser)}>
          <IonItem>
            <IonLabel position="floating">Email</IonLabel>
            <Controller
              as={<IonInput type="email" />}
              name="email"
              control={control}
              onChangeName="onIonChange"
            />
          </IonItem>
          <IonButton expand="block" type="submit" className="ion-margin-top">
            Register
          </IonButton>
        </form>
      </IonContent>
    </IonPage>
  );
};
export default Home;

Это дает нам форму с одним полем для ввода адреса электронной почты. Давайте разберем важные части.

Сначала мы деструктурируем возвращаемое значение хука useForm() из RHF. handleSubmit передает входные значения в функцию-обработчик, которую вы указываете, когда форма проходит валидацию. control — объект, содержащий методы, используемые для регистрации контролируемых компонентов в RHF.

Затем у нас есть стандартный блок элемента формы, но, в отличие от примера для формы входа, мы передаем компонент IonInput компоненту RHF <Controller />, регистрируем событие изменения, устанавливая для свойства <Controller /> onChangeName имя события изменения, а для свойства control — объект управления из вызова useForm().

Пока все хорошо, но вы можете обнаружить, что повторяете один и тот же код снова и снова. Вы можете попробовать создать повторно используемый компонент Input, который создает поле ввода с заданными свойствами.

Создайте файл с именем Input.tsx в каталоге src / components и добавьте в него приведенный ниже код:

import React, { FC } from "react";
import { IonItem, IonLabel, IonInput } from "@ionic/react";
import { Controller, Control } from "react-hook-form";

export interface InputProps {
  name: string;
  control?: Control;
  label?: string;
  component?: JSX.Element;
}

const Input: FC<InputProps> = ({
  name,
  control,
  component,
  label,
}) => {
  return (
    <>
      <IonItem>
        {label && (
          <IonLabel position="floating">{label}</IonLabel>
        )}
        <Controller
          as={component ?? <IonInput />}
          name={name}
          control={control}
          onChangeName="onIonChange"
        />
      </IonItem>
    </>
  );
};

export default Input;

Этот компонент принимает свойство name и необязательный параметр control, свойства component и label отображают поле ввода с использованием ранее представленных компонентов формы Ionic. Это сокращает объем кода, который необходимо писать при создании полей ввода формы. Вы можете завершить остальную часть формы с помощью этого компонента. Отредактируйте файл Home.tsx, внеся в него следующие изменения:

import { IonContent, IonPage, IonText, IonInput, IonButton, IonCheckbox, IonItem, IonLabel } from "@ionic/react";
import React from "react";
import "./Home.css";
import { useForm } from "react-hook-form";
import Input, { InputProps } from "../components/Input";

const Home: React.FC = () => {
  const { control, handleSubmit } = useForm();
  
  const formFields: InputProps[] = [
    {
      name: "email",
      component: <IonInput type="email" />,
      label: "Email",
    },
    {
      name: "fullName",
      label: "Full Name",
    },
    {
      name: "password",
      component: <IonInput type="password" clearOnEdit={false} />,
      label: "Password",
    },
  ];

  const registerUser = (data) => {
    console.log("creating a new user account with: ", data);
  };

  return (
    <IonPage>
      <IonContent>
        <div className="ion-padding">
          <IonText color="muted">
            <h2>Create Account</h2>
          </IonText>
          <form onSubmit={handleSubmit(registerUser)}>
            {formFields.map((field, index) => (
              <Input {...field} control={control} key={index} />
            ))}
            <IonItem>
              <IonLabel>I agree to the terms of service</IonLabel>
              <IonCheckbox slot="start" />
            </IonItem>
            <IonButton expand="block" type="submit" className="ion-margin-top">
              Register
            </IonButton>
          </form>
        </div>
      </IonContent>
    </IonPage>
  );
};

export default Home;

Теперь у нас есть массив полей ввода формы (name — это единственное обязательное свойство), причем каждое поле отображается с использованием компонента Input. Вы можете пойти еще дальше и сохранить данные полей в файле JSON, сохраняя код внутри компонентов с помощью форм. На этом этапе созданное приложение (запущенное по адресу http://localhost:8100 с помощью команды ionic serve) должно выглядеть следующим образом:

Страница формы регистрации (iOS)

Что насчет валидации полей?

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

RHF поставляется с валидацией, которая соответствует стандарту HTML для встроенной валидации форм. Это отлично подходит для простой валидации, такой как объявление поля обязательным или установка минимальной и максимальной длины поля. Если вы хотите использовать сложную логику валидации, я бы рекомендовал использовать Yup. Хотя вы можете использовать любую библиотеку проверки схемы объекта, RHF поддерживает Yup, что называется «из коробки».

Выполните приведенную ниже команду, чтобы установить библиотеку (и набор текста):

yarn add yup @types/yup

Затем добавьте это к импорту компонента:

import { object, string } from 'yup';

const Home: React.FC = () => { ... }

Затем добавьте следующий код вверху компонента:

const Home: React.FC = () => {
  const validationSchema = object().shape({
    email: string().required().email(),
    fullName: string().required().min(5).max(32),
    password: string().required().min(8),
  });
  // ...
}

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

Наконец, обновите хук useForm(), чтобы использовать схему, которую мы определили, установив свойство validationSchema следующим образом:

const { control, handleSubmit } = useForm({
  validationSchema,
});

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

Сначала обновите компонент Input, чтобы он выглядел следующим образом:

import React, { FC } from "react";
import { IonItem, IonLabel, IonInput, IonText } from "@ionic/react";
import { Controller, Control, NestDataObject, FieldError } from "react-hook-form";

export interface InputProps {
  name: string;
  control?: Control;
  label?: string;
  component?: JSX.Element;
  errors?: NestDataObject<Record<string, any>, FieldError>;
}

const Input: FC<InputProps> = ({
  name,
  control,
  component,
  label,
  errors,
}) => {
  return (
    <>
      <IonItem>
        {label && <IonLabel position="floating">{label}</IonLabel>}
        <Controller
          as={component ?? <IonInput />}
          name={name}
          control={control}
          onChangeName="onIonChange"
        />
      </IonItem>
      {errors && errors[name] && (
        <IonText color="danger" className="ion-padding-start">
          <small>{errors[name].message}</small>
        </IonText>
      )}
    </>
  );
};

export default Input;

В данном случае мы обновили компонент, чтобы получить дополнительное необязательное свойство, которое является объектом ошибки из RHF, а также мы отображаем сообщение об ошибке в возвращаемом поле ввода всякий раз, когда возникает ошибка. И последнее: добавьте объект ошибок в свой деструктурированный объект и обновите компонент в цикле:

const { control, handleSubmit, errors } = useForm({
  validationSchema,
});
  {formFields.map((field, index) => (
    <Input {...field} control={control} key={index} errors={errors} />
  ))}

Форма регистрации с сообщениями об ошибках (iOS)

Наши формы теперь предоставляют визуальные подсказки, когда пользователь что-то делает неправильно. Также вы можете изменить сообщение об ошибке. Это можно сделать, передав строку в метод валидации, который вы используете. Для электронной почты, например, можно сделать следующее:

{
  email: string()
    .email('Please provide a valid email address')
    .required('This is a required field'),
}

Улучшение доступности

Компоненты Ionic обычно являются оболочками для соответствующих нативных элементов, что означает, что они принимают большую часть (если не все) существующих атрибутов этого элемента. Вы можете улучшить поля ввода и сделать их более доступными для пользователей с ослабленным зрением, установив атрибуты ARIA с соответствующим текстом.

Чтобы продолжить работу с нашим примером формы регистрации, откройте файл Input.tsx и внесите в него следующие изменения:

import React, { FC } from "react";
import { IonItem, IonLabel, IonInput, IonText } from "@ionic/react";
import { Controller, Control, NestDataObject, FieldError } from "react-hook-form";

export interface InputProps {
  name: string;
  control?: Control;
  label?: string;
  component?: JSX.Element;
  errors?: NestDataObject<Record<string, any>, FieldError>;
}

const Input: FC<InputProps> = ({
  name,
  control,
  component,
  label,
  errors,
}) => {
  return (
    <>
      <IonItem>
        {label && <IonLabel position="floating">{label}</IonLabel>}
        <Controller
          as={
            component ?? (
              <IonInput
                aria-invalid={errors && errors[name] ? "true" : "false"}
                aria-describedby={`${name}Error`}
              />
            )
          }
          name={name}
          control={control}
          onChangeName="onIonChange"
        />
      </IonItem>
      {errors && errors[name] && (
        <IonText color="danger" className="ion-padding-start">
          <small>
            <span role="alert" id={`${name}Error`}>
              {errors[name].message}
            </span>
          </small>
        </IonText>
      )}
    </>
  );
};

export default Input;

Компонент по умолчанию, который IonInput мы передаем Controller, теперь включает в себя атрибут aria-invalid, указывающий, есть ли в поле ошибка, и атрибут aria-describedby, указывающий на соответствующее сообщение об ошибке. Сообщение об ошибке теперь заключено в span, для роли которого ARIA установлено значение «error». Теперь, когда в поле есть ошибка, программа чтения с экрана выделит это поле и зачитает сообщение об ошибке.

Заключение

Поздравляю! Вы узнали, как создавать и выполнять валидацию форм при работе с кроссплатформенными приложениями Ionic. Вы также увидели, как можно сделать поля ввода доступными для пользователей с нарушением зрения. Надеюсь, это руководство предоставит вам прочную базу, которую вы сможете использовать при создании форм в своих собственных приложениях Ionic React. Существуют и другие компоненты для создания форм (такие как select и radios), которые мы не рассмотрели в этом руководстве, но вы можете узнать о них больше в официальной документации.

Ресурсы

  • Документация Ionic Framework
  • React Hook Form
  • Документация Yup
  • Доступность — MDN

Данная публикация является переводом статьи «Forms And Validation In Ionic React» , подготовленная редакцией проекта.

Меню