Formulários e validação em Ionic React
Publicados: 2022-03-10O Ionic Framework é um kit de ferramentas de interface do usuário para criar aplicativos móveis multiplataforma usando HTML, CSS e JavaScript. O lançamento do Ionic 5 no início de 2020 veio com suporte oficial para o React, permitindo que os desenvolvedores do React criem facilmente aplicativos móveis usando suas ferramentas favoritas. Não há muito suporte para trabalhar com formulários, entretanto, e muitas das bibliotecas existentes disponíveis para construir formulários no ecossistema React não funcionam bem com os componentes do Ionic Framework.
Você aprenderá como construir formulários usando os componentes de entrada da interface do usuário do Ionic React neste tutorial. Você também aprenderá a usar uma biblioteca para ajudar a detectar alterações de entrada de formulário e responder a regras de validação. Por fim, você aprenderá a tornar seus formulários acessíveis aos leitores de tela adicionando texto útil aos atributos ARIA de suas entradas.
Componentes do formulário do Ionic
Os formulários são uma parte importante da maioria dos aplicativos móveis e da Web hoje. Esteja você habilitando o acesso a partes restritas do seu aplicativo por meio de formulários de registro e login do usuário ou coletando feedback de seus usuários, você precisa — em algum momento do ciclo de vida do seu aplicativo — criar um formulário.
O Ionic fornece componentes pré-construídos para trabalhar com formulários — alguns dos quais incluem IonItem
, IonLabel
, IonInput
, IonCheckbox
e IonRadio
. Podemos combinar esses componentes para criar formas de aparência padrão sem adicionar nenhum estilo.
Por exemplo, o seguinte código:
<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>
Nos dará um formulário de login que se parece com isso:

Fora da caixa, os componentes de formulário do Ionic ficam ótimos no iOS ou Android, mas podem ser um pouco complicados se você estiver trabalhando com o React. Como acontece com a maioria das ferramentas do ecossistema React, você precisa decidir como deseja construir seus formulários quando se trata de funcionalidade e acessibilidade – ambos tão importantes quanto o design.
Embora já existam tantos auxiliares de formulário React disponíveis para escolha, a maioria deles não funciona com os componentes de formulário do Ionic. Suspeito que a principal razão para isso seja que o evento disparado quando um valor de campo muda em Ionic é onIonChange
, enquanto a maioria das bibliotecas de formulários existentes escuta onChange
.

React Hook Form: Biblioteca de formulários React pequenos e rápidos
Felizmente, nem tudo é desgraça e melancolia. Recentemente, encontrei o React Hook Form (RHF), uma biblioteca para trabalhar com formulários em projetos React. Ele fornece suporte para componentes controlados ou não controlados e validação de entrada, e a API é baseada em ganchos, portanto, funciona apenas com componentes funcionais.
O recurso mais atraente para desenvolvedores do Ionic React — na minha opinião — é o componente wrapper <Controller />
que ele fornece para trabalhar com componentes controlados. O componente tem um prop onChangeName
que pode ser usado para especificar o nome do evento de alteração para qualquer instância do componente que você passar para ele. Mostrarei como isso facilita muito o trabalho com formulários no Ionic nas seções a seguir.
Criando um formulário de inscrição
Vamos ver como o RHF nos ajuda com a funcionalidade do formulário enquanto criamos um formulário de registro no Ionic. Se você estiver executando a versão mais recente do Ionic CLI (execute npm i -g @ionic/cli
para confirmar), inicie um novo aplicativo Ionic com React executando o seguinte comando:
ionic start myApp blank --type=react
Eu usei um modelo em branco aqui. Você deve ser capaz de reescrever seus formulários existentes para usar a biblioteca React Hook Form com facilidade, especialmente se seus componentes forem escritos como Functional Components.
Observação: você deve remover o componente ExploreContainer
e sua importação em Home.tsx antes de continuar com este tutorial.
Para começar com seu formulário, instale o pacote React Hook Form executando o seguinte comando no diretório raiz do seu projeto:
yarn add react-hook-form
Isso tornará a biblioteca React Hook Form disponível em seu projeto. Vamos criar um campo de entrada de formulário usando a biblioteca. Abra o arquivo Home.tsx e substitua seu conteúdo pelo seguinte:
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;
Isso fornece um formulário com um único campo para coletar um endereço de e-mail. Vamos detalhar as partes importantes (destacadas no bloco de código).
Primeiro, desestruturamos o valor de retorno do gancho useForm()
do RHF. handleSubmit
passa os valores de sua entrada para a função do manipulador que você especifica quando o formulário passa na validação. control
é um objeto que contém métodos usados para registrar componentes controlados no RHF.
Em seguida, temos um bloco de item de formulário padrão, mas ao contrário do exemplo para o formulário de login, passamos o componente IonInput
para o componente <Controller />
do RHF, registramos o evento de alteração definindo a propriedade onChangeName
do <Controller />
para o evento de alteração do Ionic name e defina a propriedade de control
para o objeto de controle invocando useForm()
.
Isso é bom até agora, mas você pode se encontrar repetindo quase o mesmo código várias vezes. Você pode tentar criar um componente de Input
reutilizável que construa um campo de entrada com determinadas propriedades.
Crie um arquivo no diretório src/components chamado Input.tsx e adicione o seguinte código ao arquivo:
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;
Este componente recebe um name
prop e opcional control
, component
e label
props e renderiza um campo de entrada usando os componentes de formulário Ionic introduzidos anteriormente. Isso reduz a quantidade de código que você precisa escrever ao criar campos de entrada de formulário. Você pode terminar o resto do seu formulário usando este componente. Edite o arquivo Home.tsx com as seguintes alterações:

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;
Com sua configuração até agora, você tem uma matriz de campos de entrada do seu formulário ( name
é a única propriedade obrigatória), com cada campo renderizado usando o componente Input
anterior. Você pode levar isso ainda mais longe e ter seus dados de campo em um arquivo JSON, mantendo o código dentro de seus componentes com formulários limpos. Neste ponto, seu aplicativo (executado em https://localhost:8100 com o comando ionic serve
) deve ficar assim:

Como sobre a validação de campo?
Você deve ter notado que os campos de entrada do nosso formulário ainda não possuem lógica de validação. Se este fosse um aplicativo destinado ao uso no mundo real, isso poderia levar a muitos efeitos indesejáveis, a menos que sua API esteja configurada para validar os dados recebidos. A propósito, sua API deve sempre validar os dados recebidos.
RHF vem com validação que se alinha com o padrão HTML para validação de formulário embutido. Isso funciona muito bem para validação simples, como tornar um campo obrigatório ou definir comprimentos de campo mínimo e máximo. Se você quiser usar lógica de validação complexa, eu recomendaria usar Yup. Embora você possa usar qualquer biblioteca de validação de esquema de objeto, o RHF oferece suporte ao Yup pronto para uso.
Execute o seguinte comando para instalar a biblioteca (e digitações):
yarn add yup @types/yup
Em seguida, adicione isso às importações do seu componente:
import { object, string } from 'yup'; const Home: React.FC = () => { ... }
Em seguida, adicione o seguinte código na parte superior do seu componente:
const Home: React.FC = () => { const validationSchema = object().shape({ email: string().required().email(), fullName: string().required().min(5).max(32), password: string().required().min(8), }); // ... }
Aqui, criamos um esquema de objeto e adicionamos regras de validação a cada propriedade usando yup
. Os nomes no objeto devem corresponder aos nomes nas tags de entrada do seu formulário, caso contrário, suas regras não serão acionadas.
Por fim, atualize seu gancho useForm()
para usar o esquema que definimos definindo a propriedade validationSchema
assim:
const { control, handleSubmit } = useForm({ validationSchema, });
Agora, quando você clica no botão enviar, o manipulador handleSubmit
não é invocado e os dados do formulário não são enviados. Embora isso seja exatamente o que queríamos, parece que não há como o usuário saber o que está acontecendo. Vamos corrigir isso mostrando dicas de texto quando um campo não é preenchido corretamente.
Primeiro, atualize o componente de Input
para se parecer com o seguinte:
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;
Aqui, atualizamos nosso componente para receber uma propriedade opcional extra que é o objeto de erro do RHF, e exibimos uma mensagem de erro no campo de entrada retornado sempre que houver um erro. Uma última coisa, adicione o objeto errors ao seu objeto desestruturado e atualize o componente em seu loop:
const { control, handleSubmit, errors } = useForm({ validationSchema, });
{formFields.map((field, index) => ( <Input {...field} control={control} key={index} errors={errors} /> ))}

Seus formulários agora fornecem dicas visuais quando um usuário não está fazendo algo certo. Sim, permite que você altere a mensagem de erro. Você pode fazer isso passando uma string para o método de validação que está usando. Para e-mail, por exemplo, você pode fazer o seguinte:
{ email: string() .email('Please provide a valid email address') .required('This is a required field'), }
Melhorar a acessibilidade
Os componentes do Ionic geralmente são wrappers sobre o elemento nativo correspondente, o que significa que eles aceitam a maioria — se não todos — dos atributos existentes desse elemento. Você pode melhorar seus campos de entrada e torná-los mais acessíveis para usuários com deficiência visual definindo atributos ARIA com texto relevante.
Para continuar com nosso formulário de registro de exemplo, abra o arquivo Input.tsx e faça as seguintes alterações:
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;
O componente IonInput
padrão que estamos passando para o Controller
agora inclui um atributo aria-invalid
para indicar se o campo tem um erro e um atributo aria-describedby
para apontar para a mensagem de erro correspondente. A mensagem de erro agora é encapsulada com um span
com uma função ARIA definida como "erro". Agora, quando seu campo tiver um erro, um leitor de tela destacará esse campo e lerá a mensagem de erro.
- Você encontrará o repositório do GitHub aqui.
Conclusão
Parabéns! Você aprendeu como criar e validar formulários ao criar aplicativos multiplataforma usando o Ionic. Você também viu como é fácil tornar seus campos de entrada acessíveis a usuários com deficiência visual. Felizmente, este tutorial fornece uma plataforma sólida que você pode usar ao criar formulários em seus aplicativos Ionic React. Existem outros componentes para construir formulários (como select e radios) que não exploramos neste tutorial, mas você pode encontrar e ler mais sobre eles na documentação oficial.
Referências
- Documentos do Ionic Framework
- Formulário de gancho de reação
- Sim Documentos
- Phil Haack sobre como validar endereços de e-mail
- Acessibilidade no MDN Web Docs