Formulários e validação no frontend
1. Fundamentos de Formulários HTML e JavaScript
Todo formulário web começa com uma estrutura HTML básica. Elementos como <form>, <input>, <select> e <textarea> formam a espinha dorsal da coleta de dados do usuário.
// Estrutura básica de um formulário HTML
const formHTML = `
<form id="cadastro">
<label for="nome">Nome:</label>
<input type="text" id="nome" name="nome" />
<label for="email">E-mail:</label>
<input type="email" id="email" name="email" />
<label for="pais">País:</label>
<select id="pais" name="pais">
<option value="">Selecione</option>
<option value="br">Brasil</option>
<option value="us">Estados Unidos</option>
</select>
<label for="bio">Biografia:</label>
<textarea id="bio" name="bio"></textarea>
<button type="submit">Enviar</button>
</form>
`;
Para capturar dados com JavaScript puro, utilizamos document.forms e as propriedades dos inputs:
// Capturando dados do formulário
const form = document.forms['cadastro'];
form.addEventListener('submit', function(event) {
event.preventDefault(); // Evita recarregamento da página
const dados = {
nome: form.elements['nome'].value,
email: form.elements['email'].value,
pais: form.elements['pais'].value,
bio: form.elements['bio'].value
};
console.log('Dados do formulário:', dados);
});
Os principais eventos de formulário são:
- submit: disparado ao enviar o formulário
- input: disparado a cada alteração no valor do campo
- change: disparado quando o valor é confirmado (perda de foco)
- focus/blur: quando o campo ganha ou perde foco
2. Validação Nativa do HTML5 vs. Validação Customizada
O HTML5 oferece atributos de validação nativos que são fáceis de implementar:
// Exemplo de formulário com validação HTML5
const formComValidacao = `
<form id="validacao-nativa">
<input type="text" required minlength="3" maxlength="50" />
<input type="email" required />
<input type="password" required pattern=".{8,}" />
<input type="number" min="18" max="120" />
<button type="submit">Enviar</button>
</form>
`;
No entanto, a validação nativa tem limitações:
- Mensagens de erro padronizadas e difíceis de personalizar
- Estilo visual limitado
- Comportamento inconsistente entre navegadores
Para superar essas limitações, implementamos validação customizada:
// Validação customizada com JavaScript puro
function validarCampo(input) {
const valor = input.value.trim();
let mensagemErro = '';
if (input.hasAttribute('required') && !valor) {
mensagemErro = 'Este campo é obrigatório';
} else if (input.type === 'email' && valor) {
const regexEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!regexEmail.test(valor)) {
mensagemErro = 'E-mail inválido';
}
}
// Exibe ou remove mensagem de erro
const erroEl = input.nextElementSibling;
if (mensagemErro) {
input.classList.add('invalido');
erroEl.textContent = mensagemErro;
erroEl.style.display = 'block';
} else {
input.classList.remove('invalido');
erroEl.style.display = 'none';
}
return !mensagemErro;
}
3. Técnicas de Validação com JavaScript Puro
Vamos implementar validações específicas para campos comuns:
// Validação de e-mail
function validarEmail(email) {
const regex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
return regex.test(email);
}
// Validação de CPF (apenas dígitos verificadores)
function validarCPF(cpf) {
cpf = cpf.replace(/\D/g, '');
if (cpf.length !== 11 || /^(\d)\1{10}$/.test(cpf)) return false;
let soma = 0;
for (let i = 0; i < 9; i++) soma += parseInt(cpf[i]) * (10 - i);
let resto = (soma * 10) % 11;
if (resto === 10) resto = 0;
if (resto !== parseInt(cpf[9])) return false;
soma = 0;
for (let i = 0; i < 10; i++) soma += parseInt(cpf[i]) * (11 - i);
resto = (soma * 10) % 11;
if (resto === 10) resto = 0;
return resto === parseInt(cpf[10]);
}
// Validação de senha forte
function validarSenha(senha) {
const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
return regex.test(senha);
}
Para validação em tempo real, usamos os eventos input e blur:
// Validação em tempo real
const campoEmail = document.getElementById('email');
const mensagemErro = document.getElementById('erro-email');
campoEmail.addEventListener('input', function() {
if (this.value && !validarEmail(this.value)) {
mensagemErro.textContent = 'Formato de e-mail inválido';
this.classList.add('invalido');
} else {
mensagemErro.textContent = '';
this.classList.remove('invalido');
}
});
campoEmail.addEventListener('blur', function() {
if (!this.value) {
mensagemErro.textContent = 'E-mail é obrigatório';
this.classList.add('invalido');
}
});
4. Validação no Frontend com React – Estado e Eventos
No React, gerenciamos formulários com estado usando useState:
import React, { useState } from 'react';
function FormularioContato() {
const [formData, setFormData] = useState({
nome: '',
email: '',
mensagem: ''
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('Dados enviados:', formData);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
name="nome"
value={formData.nome}
onChange={handleChange}
placeholder="Nome"
/>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
placeholder="E-mail"
/>
<textarea
name="mensagem"
value={formData.mensagem}
onChange={handleChange}
placeholder="Mensagem"
/>
<button type="submit">Enviar</button>
</form>
);
}
5. Validação com React – Abordagens e Bibliotecas
Para validação manual, podemos criar um estado de erros:
function FormularioValidado() {
const [formData, setFormData] = useState({ email: '', senha: '' });
const [erros, setErros] = useState({});
const validar = () => {
const novosErros = {};
if (!formData.email) {
novosErros.email = 'E-mail é obrigatório';
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
novosErros.email = 'E-mail inválido';
}
if (!formData.senha) {
novosErros.senha = 'Senha é obrigatória';
} else if (formData.senha.length < 8) {
novosErros.senha = 'Mínimo de 8 caracteres';
}
setErros(novosErros);
return Object.keys(novosErros).length === 0;
};
const handleSubmit = (e) => {
e.preventDefault();
if (validar()) {
// Enviar dados
}
};
return (
<form onSubmit={handleSubmit}>
<input
name="email"
value={formData.email}
onChange={handleChange}
aria-invalid={!!erros.email}
aria-describedby={erros.email ? 'erro-email' : undefined}
/>
{erros.email && <span id="erro-email">{erros.email}</span>}
{/* ... */}
</form>
);
}
Com React Hook Form, a validação fica mais concisa:
import { useForm } from 'react-hook-form';
function FormularioComHook() {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = (data) => {
console.log('Dados válidos:', data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
{...register('email', {
required: 'E-mail é obrigatório',
pattern: {
value: /\S+@\S+\.\S+/,
message: 'E-mail inválido'
}
})}
/>
{errors.email && <span>{errors.email.message}</span>}
<input
{...register('senha', {
required: 'Senha é obrigatória',
minLength: {
value: 8,
message: 'Mínimo de 8 caracteres'
}
})}
type="password"
/>
{errors.senha && <span>{errors.senha.message}</span>}
<button type="submit">Enviar</button>
</form>
);
}
6. Validação Assíncrona e Integração com APIs
Para validar contra o backend (ex: e-mail já cadastrado):
import { useForm } from 'react-hook-form';
function FormularioAssincrono() {
const { register, handleSubmit, formState: { errors } } = useForm();
const validarEmailUnico = async (email) => {
try {
const response = await fetch(`/api/verificar-email?email=${email}`);
const data = await response.json();
return data.disponivel || 'E-mail já cadastrado';
} catch (error) {
return 'Erro ao verificar e-mail';
}
};
const onSubmit = async (data) => {
try {
const response = await fetch('/api/cadastro', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error('Erro no servidor');
}
alert('Cadastro realizado com sucesso!');
} catch (error) {
alert('Erro de rede. Tente novamente.');
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
{...register('email', {
required: 'E-mail é obrigatório',
validate: validarEmailUnico
})}
/>
{errors.email && <span>{errors.email.message}</span>}
<button type="submit" disabled={Object.keys(errors).length > 0}>
Cadastrar
</button>
</form>
);
}
7. Boas Práticas, Acessibilidade e UX
Para uma experiência de usuário de qualidade:
// Componente de input acessível
function InputAcessivel({ label, erro, ...props }) {
const id = `campo-${props.name}`;
const erroId = `erro-${props.name}`;
return (
<div>
<label htmlFor={id}>{label}</label>
<input
id={id}
aria-invalid={!!erro}
aria-describedby={erro ? erroId : undefined}
{...props}
/>
{erro && (
<span id={erroId} role="alert" style={{ color: 'red' }}>
{erro}
</span>
)}
</div>
);
}
Boas práticas essenciais:
- Mensagens claras: "E-mail inválido" em vez de "Erro no campo"
- Posicionamento: mensagens próximas ao campo correspondente
- Feedback visual: bordas vermelhas para erro, verdes para sucesso
- Foco automático: levar o foco ao primeiro campo com erro
- Desabilitar submit: impedir envio enquanto houver erros
- Acessibilidade: usar aria-invalid, aria-describedby e role="alert"
// Foco automático no primeiro campo com erro
useEffect(() => {
const primeiroErro = document.querySelector('[aria-invalid="true"]');
if (primeiroErro) {
primeiroErro.focus();
}
}, [erros]);
A validação no frontend não é apenas sobre impedir dados inválidos — é sobre guiar o usuário com clareza, eficiência e respeito ao seu tempo. Combinando HTML semântico, JavaScript estratégico e React com boas bibliotecas, construímos formulários que funcionam bem para todos.
Referências
- MDN Web Docs: Validação de formulários — Guia completo sobre validação nativa e customizada com JavaScript
- React Hook Form - Documentação Oficial — Biblioteca de formulários para React com suporte a validação assíncrona
- W3C: ARIA em formulários — Boas práticas de acessibilidade em formulários web
- DigitalOcean: Validação de CPF com JavaScript — Tutorial prático de validação de campos comuns
- React Documentation: Forms — Documentação oficial sobre inputs controlados e formulários em React
- Stack Overflow: Validação assíncrona com React Hook Form — Exemplos de validação assíncrona na prática