Gerenciando formulários no React Native

Se você já construiu algumas telas com campos de texto ou informações preenchidas pelo usuário sabe como é verboso salvar os campos no estado do componente e não ter um encapsulamento de formulário assim como tempos no HTML em que todos inputs são facilmente recuperados com pouco código.

Provavelmente seu primeiro TextInput no React Native gerou um código muito parecido com esse:

<TextInput
  onChangeText=(text => this.setState({ name: text }));
/>

Agora imagine uma tela com 40 TextInput’s, quanto código seria gerado? Quantos setState teríamos em uma única tela? Isso porque no React você não controla a DOM através de classes ou ID’s e toda atualização de componente deve partir diretamente da view (sem eventListeners criados fora do render). Além disso, imagine adicionar validação em todos campos. Validação de e-mail? Apenas números? O problema fica cada vez maior.

TComb Form Native

A primeira lib que resolvi olhar foi o TComb Form Native, o grande barato dessa lib é poder escrever um único componente no render e gerar o form inteiro a partir de um objeto que define os tipos dos campos.

Apesar de muito prática essa ferramenta possui limitações quando se trata do React Native, principalmente quando seus inputs são personalizados e se comportam de uma maneira diferente do padrão.

Recomendo usar essa biblioteca se seus forms forem muito simples e você precise de praticidade.

Redux-form

A mais famosa lib de formulários do ecossistema React. Apesar da lib realizar grande parte do gerenciamento do estado dos inputs do form e permitir customizar os campos e trabalhar de uma forma menos verbosa, eu (e muitas outras pessoas) não sou um grande fã de manter o estado dos campos do formulário do lado do Redux.

Formik

Depois de procurar em diversas libs, alguns usuários do Comunidade da RocketSeat me lembraram do Formik e resolvi experimentar novamente essa biblioteca.

Algo que nós devs sempre buscamos é tentar escrever a menor quantidade de código pra gerar a maior quantidade de funcionalidades, mas nem sempre isso é saudável, ou quase nunca.

A grande diferença do Formik para as outras bibliotecas de formulários é que ele não tenta fazer mágica, ao invés disso, essa lib encapsula nosso formulário oferecendo maneiras mais simples de tratarmos as validações, submits, valores padrão, erros, etc.

Para começar, podemos instalar o Formik com o comando:

yarn add formik

// Ou npm install --save formik
import React from 'react';
import { View, TextInput, Button } from 'react-native';

import { withFormik } from 'formik';

const Form = (props) => (
  <View style={styles.container}>
    <TextInput
      value={props.values.email}
      onChangeText={text => props.setFieldValue('email', text)}
    />

    <TextInput
      value={props.values.password}
      onChangeText={text => props.setFieldValue('password', text)}
    />

    <Button
      onPress={props.handleSubmit}
      title="Login"
    />
  </View>
);

export default withFormik({
  mapPropsToValues: () => ({ email: '', password: '' }),

  handleSubmit: (values) => {
    console.log(values);
  }
})(Form);

Escrevemos nosso formulário da mesma forma que escreveríamos sem utilizar a biblioteca, porém no lugar do setState no método onChangeTextutilizamos a função setFieldValue que é embutida nas propriedades do componente através da composição realizada no final do arquivo com o withFormik.

Da mesma forma, temos um método handleSubmit que podemos chamar através do onPress do botão para realizar o envio do form.

Perceba que utilizo a função withFormik para compor meu componente Form da mesma forma que utilizamos no connect do Redux e como parâmetro passo um objeto com duas propriedades:

  • mapPropsToValues: uma função que recebe nosso estado inicial dos inputs, pode receber as props do componente como parâmetro para customizar os valores.
  • handleSubmit: executada no submit do formulário, responsável por realizar a tratativa dos dados e retornar os erros de API (não de validação) caso necessário. A variável values é um objeto com cada campo e seu valor, por exemplo: {email: 'diego@exemplo.com', password: 123456}.

Até agora não existe muita vantagem, certo? Vamos ver do que o Formik é capaz.

Loading

O Formik oferece uma propriedade isSubmitting que contém a informação de loading do handleSubmit, assim conseguimos exibir uma informação de carregamento no componente:

{ props.isSubmitting && <ActivityIndicator /> }

Erros

Pense que precisemos exibir uma mensagem de erro (não erro de validação) caso a requisição à API tenha falhado, com o Formik fica muito fácil:

handleSubmit: (values, { setSubmitting, setErrors }) => {
  apiService.post('/authenticate', values)
    .then(/* sucesso */)
    .catch(err => {
      setSubmitting(false);
      setErrors({ message: err.message });
    });
}

Utilizamos o método setSubmitting para indicar que não estamos mais realizando o loading e o método setErrors para preencher a propriedade de erros. Com isso, agora podemos exibir o erro em tela com a propriedade errors:

{ props.errors.message && <Text>{props.errors.message}</Text> }

asassad

Validação

A validação dos campos é um dos maiores pontos positivos do Formik. Para obter o máximo da validação, vamos utilizar um pacote chamando Yup que nos ajudará na hora de definir as regras de cada campo, para instalá-lo basta utilizar o comando:

yarn add yup  // Utilize NPM se preferir

E importá-lo utilizando:

import Yup from 'yup';

A validação é adicionada no objeto passado como parâmetro da função withFormik, vamos ver como funciona um exemplo de validação de e-mail e senha simples:

export default withFormik({
  mapPropsToValues: () => ({ email: '', password: '' }),

  validationSchema: Yup.object().shape({
    email: Yup.string()
      .email('Digite um e-mail válido')
      .required('Preencha o campo de e-mail'),
    password: Yup.string()
      .min(6, 'A senha deve ter no mínimo 6 caracteres')
      .required('Preencha o campo de senha'),
  }),

  handleSubmit: (values) => {
    ...
  }
})(App);

Sempre começamos com o método .object e .shape para indicar nossas validações e seguimos com os nomes dos campos nas propriedades e as validações logo após. Você pode encontrar uma lista de todas validações possível nesse link.

Agora, quando realizamos o submit e uma das validações falhar, podemos acessá-las também com o objeto errors:

{ props.errors.email && <Text>{props.errors.email}</Text> }
Fácil, não?

Por padrão as validações são feitas enquanto o usuário digita, você pode desabilitar essa opção repassando o validateOnChange: false para o objeto parâmetro do withFormik. Se quiser manter esse tipo de validação porém não exibir o erro na primeira digitação do usuário, basta unir a propriedade touched enviada ao componente com a propriedade errorspara validar se um campo já foi preenchido pelo menos uma vez:

{ props.touched.email 
  && props.errors.email 
  && <Text>{props.errors.email}</Text> }

Concluindo

Apesar de ter passado pelas principais funcionalidades do Formik nesse post, a biblioteca ainda possui muitas opções de configuração, validações customizadas, etc, recomendo muito ir mais a fundo nisso de acordo com que sua aplicação necessite por serem opções mais avançadas.

Se você gostou desse post não esquece de deixar um comentário e suas ?