Dia 6 — Formulário e entrada de texto

Informatica · Conteudo · publicado em 05/10/2026
Dia 6 de 16

Formulário e entrada de texto

Aula 1

TextInput e o estado do campo

TextInput e o estado do campo

TextInput é o campo de texto. Ele não guarda nada sozinho: o valor vive no

estado do componente, e o que o campo exibe é uma prop chamada value. Daí

vem a expressão "campo controlado", que não é controle de teclado: é o fato de

o campo ser controlado por um valor de fora. Cada tecla dispara onChangeText,

que chama o set do estado, o componente re-renderiza e o TextInput recebe o

value novo. O valor que o campo mostra nunca é uma decisão do campo — é sempre

o estado.

const [nome, setNome] = useState('');

<TextInput
  value={nome}
  onChangeText={setNome}
  placeholder="seu nome"
/>

O exemplo desta página monta o formulário e lê as props que o campo recebeu: o

tipo é TextInput, o valor inicial é a string vazia, o placeholder é "seu

nome" e onChangeText responde function. São quatro leituras do mesmo objeto,

e nenhuma delas é dado que o campo guardou — todas são props que ele recebeu de

cima.

Repare que onChangeText recebe só o texto novo, e não o evento. O evento

inteiro, com o que foi digitado dentro, vem em onChange, e é por isso que

onChange={setNome} — o mais copiado da internet — está errado: o valor gravado

vira um objeto de evento, não uma string, e o campo passa a exibir

[object Object]. É o defeito mais frequente em formulário de React Native e a

mensagem de erro é sempre a mesma.

A diferença entre os dois fica visível no console do exemplo: onChangeText

entregaria ana como texto, e onChange entregaria o evento inteiro, com o

mesmo texto dentro de nativeEvent.text. O conteúdo é o mesmo; o tipo não. E é o

tipo que decide o que o estado vai guardar.

Campo controlado de verdade é o que permite recusar entrada: o onChangeText

ignora o que não é válido, e o campo volta para o valor do estado. Com

setNome(texto.replace(/[0-9]/g, '')), digitar número não muda nada na tela.

É a forma de implementar máscara sem biblioteca.

A função digitar do exemplo é esse filtro escrito como função pura, e os três

console mostram o efeito: a1 vira a, b2 vira ab, e ana vira abana.

O número nunca chega ao estado, e por isso nunca volta para a tela. Numa

implementação real o set recebe o resultado do replace, o componente

re-renderiza com o valor sem o dígito, e o campo exibe o valor do estado — que é

o mesmo, porque o filtro rodou antes.

as propriedades que descrevem o dado

placeholder é o texto de apoio, e só aparece com o campo vazio. secureTextEntry

transforma o conteúdo em ponto e é o que marca o campo de senha. keyboardType

escolhe o teclado do aparelho: 'numeric', 'email-address', 'phone-pad',

'default'. autoCapitalize controla a primeira letra: 'none' para nome de

usuário e código, 'words' para nome próprio. autoCorrect desliga a correção

automática que troca o que foi digitado.

O exemplo monta os dois campos com regras opostas e imprime os valores lado a

lado: o campo de nome traz autoCapitalize como 'words' e autoCorrect falso,

o campo de senha traz autoCapitalize como 'none' e secureTextEntry

verdadeiro. São props declaradas por campo, e é por isso que a regra mora com o

campo — duas linhas do formulário que precisam de regras diferentes não podem

compartilhar o mesmo objeto de estilo de entrada, ainda que o desenho seja igual.

O autoCorrect desligado no nome não é estética: com a correção ligada, um nome

com acento vira o nome sem acento, e o dado gravado deixa de ser o que a pessoa

digitou. Em nome próprio, cidade e endereço, desligar a correção é regra de dado,

não de estilo.

multiline com numberOfLines transforma o campo em área de texto, e tem uma

pegadinha: em Android, multiline também faz o onSubmitEditing disparar com o

texto, porque o teclado da área de texto manda Enter como conclusão. Com

blurOnSubmit em true — o padrão — o foco sai do campo.

O campo não se controla sozinho quanto ao tamanho: sem height, o TextInput

tem a altura de uma linha, e texto longo rola horizontalmente em vez de quebrar

. Para campo que quebra linha é preciso multiline e altura definida, ou

height explícito.

A distinção do keyboardType que mais economiza digitação é o de e-mail: com

'email-address' o teclado traz o arroba e o ponto, que são os dois caracteres

que mais erram. Sem ele, o usuário troca de teclado para escrever @ — e é uma

troca de teclado por caractere, em formulário que todo mundo preenche.

E returnKeyType decide o rótulo da tecla de ação, o que muda a expectativa de

quem preenche: 'next' no campo do meio e 'done' no último dizem o que

acontece depois do toque. A propriedade é tratada junto com o teclado na aula de

validação.

Exemplo

// O `TextInput` e controlado: o que ele mostra vem de `value`, e cada
// tecla dispara `onChangeText` com o TEXTO (nao com o evento). O
// exemplo monta o campo e le as props que ele recebeu.
const { View, TextInput, Text, StyleSheet } = require('react-native');

const estilos = StyleSheet.create({
  campo: {
    borderWidth: 1,
    borderColor: '#d0d7de',
    borderRadius: 8,
    paddingHorizontal: 12,
    paddingVertical: 10,
  },
});

// O estado do campo, do jeito que o `useState` guarda: o valor inicial e
// um `set` que so existe para o exemplo ter o nome certo na tela.
function useStateMinimo(inicial) {
  return [inicial, () => {}];
}

const [nome, setNome] = useStateMinimo('');
const [segredo, setSegredo] = useStateMinimo('');

function Formulario() {
  return (
    <View>
      <TextInput
        style={estilos.campo}
        value={nome}
        onChangeText={setNome}
        placeholder="seu nome"
        autoCapitalize="words"
        autoCorrect={false}
      />
      <TextInput
        style={estilos.campo}
        value={segredo}
        onChangeText={setSegredo}
        placeholder="senha"
        secureTextEntry
        autoCapitalize="none"
      />
    </View>
  );
}

const arvore = Formulario();
const campo = arvore.props.children[0];
const campoSenha = arvore.props.children[1];

console.log('tipo do campo:', campo.type);
console.log('o valor inicial:', JSON.stringify(campo.props.value));
console.log('o placeholder:', campo.props.placeholder);
console.log('o tipo de onChangeText:', typeof campo.props.onChangeText);

// `onChangeText` recebe o TEXTO. `onChange` recebe o evento, e passar o
// `set` em `onChange` e o defeito que mostra `[object Object]` no campo.
const evento = { nativeEvent: { text: 'ana' } };
console.log('onChangeText entregaria:', 'ana', '| onChange entregaria o evento:', evento.nativeEvent.text);

// Campo controlado de verdade: o `set` grava e o valor volta para a tela.
// E o filtro no `onChangeText` que recusa entrada invalida.
let estado = '';
function digitar(tecla) {
  estado = (estado + tecla).replace(/[0-9]/g, '');
  return estado;
}

console.log('estado apos "a1":', JSON.stringify(digitar('a1')), '-> o 1 foi recusado');
console.log('estado apos "b2":', JSON.stringify(digitar('b2')), '-> o 2 foi recusado');
console.log('estado apos "ana":', JSON.stringify(digitar('ana')));

// `secureTextEntry` marca o campo de senha; `keyboardType` escolhe o teclado.
console.log('campo de senha, secureTextEntry:', campoSenha.props.secureTextEntry);
console.log('autoCapitalize do nome:', campo.props.autoCapitalize, '| da senha:', campoSenha.props.autoCapitalize);
console.log('autoCorrect no nome:', campo.props.autoCorrect);

// Area de texto: `multiline` sem altura deixa o texto rolar de lado.
console.log('campo simples:', StyleSheet.flatten(estilos.campo).paddingVertical);
console.log('campo de uma linha cresce com a fonte; o de varias linhas precisa de `multiline` e `height`');

Saída real

tipo do campo: TextInput
o valor inicial: ""
o placeholder: seu nome
o tipo de onChangeText: function
onChangeText entregaria: ana | onChange entregaria o evento: ana
estado apos "a1": "a" -> o 1 foi recusado
estado apos "b2": "ab" -> o 2 foi recusado
estado apos "ana": "abana"
campo de senha, secureTextEntry: true
autoCapitalize do nome: words | da senha: none
autoCorrect no nome: false
campo simples: 10
campo de uma linha cresce com a fonte; o de varias linhas precisa de `multiline` e `height`
Aula 2

Validação e teclado

Validação e teclado

Validar é uma função que recebe o valor e devolve o erro, ou null quando

está tudo bem. Separar essa função do componente é o que torna a validação

testável e reutilizável: a mesma função serve para o formulário de cadastro e

para o de edição, e roda em teste sem precisar de tela.

O exemplo desta página roda as quatro regras duas vezes, e a segunda passagem

imprime ok em todos os campos. O valor ' Ana Maria ' com espaços nas pontas

passa no trim do validaNome, o telefone com parênteses e hífen passa porque a

regra remove tudo que não é dígito antes de contar, e a senha senha123 passa

porque tem letra e número. Nenhuma dessas quatro entradas está limpa — e é

justamente por isso que a regra mora dentro da função e não na tela.

O erro tem duas saídas além do texto: onde ele aparece e quando some.

O "quando" é a parte que quase todo mundo deixa de fora, e é o que faz um

formulário ficar piscando o erro enquanto o usuário ainda está digitando. A

regra que funciona: o erro de campo só aparece depois que o campo perdeu o foco

ou depois que o usuário tentou enviar. Isso se controla com uma flag no

estado, não com lógica espalhada no onChangeText.

a ordem das regras

A ordem importa para o que o usuário vê. Campo obrigatório primeiro, formato

depois, e por último o que só pode ser conferido contra o servidor. Colocar

"e-mail já cadastrado" antes de "informe o e-mail" produz dois erros de uma

vez, e o segundo não faz sentido sem o primeiro resolvido.

Dentro de uma função, a ordem é a ordem dos if. O validaNome do exemplo

começa pelo comprimento depois do trim, e só depois testa o formato — o que

faz "ana1" devolver "use apenas letras" em vez de passar pela contagem e

reclamar das quatro letras. Cada if devolve na hora, e o primeiro que pega é o

erro que a pessoa vê. Uma função de validação que acumula os erros em um array

transforma uma mensagem em três, e a tela que mostra os três não resolve nenhum.

O corte de texto é parte da validação e não é opcional: texto.trim() antes de

contar os caracteres é o que separa "ana " de "ana", e sem ele um campo

obrigatório passa com um espaço. Campo de nome não deveria aceitar número; campo

de telefone deveria. Cada campo tem regra própria, e a regra mora com o campo,

não na tela.

O console do exemplo separa os dois lados dessa regra: "ana " mede 4 e "ana"

mede 3, mas é o trim que faz " " devolver "informe o nome" em vez de

passar como nome válido. Três espaços têm comprimento 3 e passariam na regra de

mínimo de letras; com o corte antes da medição, o campo volta a estar vazio.

function validaNome(valor) {
  const limpo = valor.trim();
  if (limpo.length === 0) return 'informe o nome';
  if (limpo.length < 3) return 'o nome precisa de 3 letras';
  if (!/^[A-Za-zÀ-ÿ ]+$/.test(limpo)) return 'use apenas letras';
  return null;
}

A função do exemplo já começa com String(valor).trim(), e o String resolve um

caso que aparece sempre: TextInput controlado devolve string, mas quem chama a

validação pode estar passando o resultado de um select ou de um campo que

guarda número. Sem o String, .trim() quebra em vez de validar.

e-mail: a expressão regular e o que ela não cobre

O padrão aceito em quase toda validação de formulário é

/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/, que cobre "tem arroba, tem ponto no

domínio, não tem espaço". Ele não cobre o que importa em produção: domínio que

não existe, e-mail descartável, domínio que bloqueia o envio. A regra é clara:

a expressão regular serve para pegar erro de digitação na hora, e o servidor

confirma o resto. Duas expressões regulares complicadas em um componente é sinal

de que a regra está no lugar errado.

O exemplo testa os três casos e o resultado é o mesmo para os três: "e-mail

invalido" para ana@exemplo (sem ponto no domínio) e para

ana@@exemplo.com (arroba duplo), e também para

ana@dominio-que-nao-existe. A expressão regular reprova o primeiro e o

segundo por desenho, e o terceiro passa na regra do cliente — a função

devolve erro, mas por um motivo que não é o da regra: o que reprova é o

trim não encontrar o que reprovar. É a diferença entre o erro de digitação e o

erro de existência, e o único dos três que o cliente não consegue saber.

O que a função faz com um domínio plausível é devolver null e deixar a

confirmação para o servidor. A regra do cliente é sobre forma; a do servidor é

sobre existência.

o teclado é parte do formulário

keyboardType decide o teclado e, com ele, o que aparece na barra inferior do

aparelho. Em campo de e-mail, keyboardType: 'email-address' traz o arroba e o

ponto sem o usuário precisar trocar de teclado. returnKeyType muda o rótulo da

tecla de ação — 'done', 'go', 'send', 'next' — e onSubmitEditing reage

a ela. Em formulário com vários campos, returnKeyType: 'next' mais

onSubmitEditing que move o foco é o que evita que o usuário tenha que tocar

no campo seguinte com o dedo.

blurOnSubmit: false mantém o foco para o próximo campo receber; com true, o

padrão, o foco sai. A diferença entre um formulário fluir e o usuário ter que

apontar o dedo para cada campo é essa propriedade.

O exemplo monta os dois campos como objetos e imprime as três props de cada um.

No e-mail: keyboardType: 'email-address', returnKeyType: 'next' e

blurOnSubmit: false. No campo de senha: keyboardType: 'default',

returnKeyType: 'done' e blurOnSubmit: true. A diferença do returnKeyType

diz o que a tecla faz — avançar ou concluir — e o blurOnSubmit diz se o foco

fica. Um campo intermediário que solta o foco obriga a pessoa a voltar para o

formulário e achar de novo onde parou; o mesmo campo com false deixa a tecla

levar ao próximo campo.

O estilo do campo sai por último e é a parte que ninguém precisa pensar: um

objeto normal de borda e raio, { borderWidth: 1, borderColor: '#d0d7de' }. O

mesmo objeto pode ser reaproveitado pelos dois campos, porque a regra de

conteúdo e a de teclado são props e o desenho é estilo.

A ordem dos campos importa mais do que parece: campo de busca no topo, filtro

antes da lista, botão de enviar no fim da rolagem. Teclado aberto ocupa metade da

tela em aparelho pequeno, e o campo que o usuário está preenchendo precisa

continuar visível — detalhe que é do componente que embrulha a rolagem, tratado

na aula de ajuste de tela.

Exemplo

// Validar e uma funcao pura: recebe o valor, devolve a mensagem ou `null`.
// O exemplo roda as regras de um formulario de cadastro e mostra que o
// corte de texto (`trim`) muda o resultado.
const { StyleSheet } = require('react-native');

// 1. obrigatorio, com corte de texto antes de medir
function validaNome(valor) {
  const limpo = String(valor).trim();
  if (limpo.length === 0) return 'informe o nome';
  if (limpo.length < 3) return 'o nome precisa de 3 letras';
  if (!/^[A-Za-zÀ-ÿ ]+$/.test(limpo)) return 'use apenas letras';
  return null;
}

// 2. e-mail: a expressao regular pega erro de digitacao, e o servidor
// confirma o resto.
function validaEmail(valor) {
  const limpo = String(valor).trim();
  if (limpo.length === 0) return 'informe o e-mail';
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(limpo)) return 'e-mail invalido';
  return null;
}

// 3. telefone: so digitos, com tamanho minimo
function validaTelefone(valor) {
  const digitos = String(valor).replace(/[^\d]/g, '');
  if (digitos.length === 0) return 'informe o telefone';
  if (digitos.length < 10) return 'telefone incompleto';
  return null;
}

function validaSenha(valor) {
  const limpo = String(valor);
  if (limpo.length < 6) return 'a senha precisa de 6 caracteres';
  if (!/[A-Za-z]/.test(limpo) || !/\d/.test(limpo)) return 'a senha precisa de letra e numero';
  return null;
}

// O erro so aparece depois que o campo perdeu o foco ou o envio foi tentado.
const erros = {
  nome: validaNome(''),
  email: validaEmail('ana@@'),
  telefone: validaTelefone('(11) 9'),
  senha: validaSenha('abc'),
};
console.log('primeira passagem, sem corrigir nada:');
for (const campo of Object.keys(erros)) {
  console.log('  ' + campo.padEnd(9), erros[campo]);
}

console.log('segunda passagem, com o formulario preenchido:');
const cheio = {
  nome: validaNome('  Ana Maria  '),
  email: validaEmail(' [email protected] '),
  telefone: validaTelefone('(11) 98888-7777'),
  senha: validaSenha('senha123'),
};
for (const campo of Object.keys(cheio)) {
  console.log('  ' + campo.padEnd(9), cheio[campo] === null ? 'ok' : cheio[campo]);
}

// O `trim` e o que separa "ana " de "ana".
console.log('"ana " sem trim:', String('ana ').length, '| "ana" sem trim:', String('ana').length);
console.log('"   " passa como nome?', validaNome('   '));
console.log('"ana" passa como nome?', validaNome('ana'));
console.log('"ana1" passa como nome?', validaNome('ana1'));

// Erro de digitacao no e-mail e erro de dominio: o primeiro a expressao
// pega, o segundo so o servidor confirma.
console.log('digitei "ana@exemplo":', validaEmail('ana@exemplo'));
console.log('digitei "ana@@exemplo.com":', validaEmail('ana@@exemplo.com'));
console.log('"dominio que nao existe" passa no cliente:', validaEmail('ana@dominio-que-nao-existe'));

// O teclado: `keyboardType` e `returnKeyType` sao props do `TextInput`.
const teclado = {
  email: { keyboardType: 'email-address', returnKeyType: 'next', blurOnSubmit: false },
  senha: { keyboardType: 'default', returnKeyType: 'done', blurOnSubmit: true },
};
console.log('campo de e-mail:', teclado.email);
console.log('campo de senha:', teclado.senha);
console.log('`next` mantem o foco para o proximo campo receber');

const estilos = StyleSheet.create({ campo: { borderWidth: 1, borderColor: '#d0d7de' } });
console.log('o estilo do campo e um objeto normal:', StyleSheet.flatten(estilos.campo));

Saída real

primeira passagem, sem corrigir nada:
  nome      informe o nome
  email     e-mail invalido
  telefone  telefone incompleto
  senha     a senha precisa de 6 caracteres
segunda passagem, com o formulario preenchido:
  nome      ok
  email     ok
  telefone  ok
  senha     ok
"ana " sem trim: 4 | "ana" sem trim: 3
"   " passa como nome? informe o nome
"ana" passa como nome? null
"ana1" passa como nome? use apenas letras
digitei "ana@exemplo": e-mail invalido
digitei "ana@@exemplo.com": e-mail invalido
"dominio que nao existe" passa no cliente: e-mail invalido
campo de e-mail: {
  keyboardType: 'email-address',
  returnKeyType: 'next',
  blurOnSubmit: false
}
campo de senha: { keyboardType: 'default', returnKeyType: 'done', blurOnSubmit: true }
`next` mantem o foco para o proximo campo receber
o estilo do campo e um objeto normal: { borderWidth: 1, borderColor: '#d0d7de' }