Formulários controlados e não controlados no React
1. Introdução aos Formulários no React
1.1. O papel dos formulários em aplicações React modernas
Formulários são a espinha dorsal da interação usuário-sistema em aplicações web. No ecossistema React, eles ganham uma camada extra de complexidade e poder, pois o React introduz uma abordagem declarativa para gerenciar o estado da interface. Diferente do HTML tradicional, onde o DOM mantém seu próprio estado interno, no React você decide quem controla os dados: o componente React (controlado) ou o próprio DOM (não controlado).
1.2. Diferença fundamental entre formulários tradicionais HTML e React
No HTML puro, quando um usuário digita em um campo <input>, o navegador gerencia automaticamente o valor exibido. No React, você precisa explicitamente definir o value do input e atualizá-lo via onChange. Essa diferença é a base para entender os dois paradigmas.
1.3. Visão geral: estado gerenciado pelo React vs. estado gerenciado pelo DOM
- Controlado: O React é a "única fonte da verdade". Cada alteração no input dispara uma função que atualiza o estado do componente, que por sua vez re-renderiza o input com o novo valor.
- Não controlado: O DOM mantém seu próprio estado. Você acessa os valores apenas quando necessário, geralmente via
refs.
2. Formulários Controlados: Conceito e Implementação
2.1. O que são componentes controlados: estado como única fonte de verdade
Um componente controlado é aquele onde o valor do input é definido pelo estado do React e só muda através de um event handler. Isso dá controle total sobre o comportamento do formulário.
2.2. Criando um input controlado com useState e onChange
import { useState } from 'react';
function FormularioControlado() {
const [nome, setNome] = useState('');
const handleChange = (evento) => {
setNome(evento.target.value);
};
const handleSubmit = (evento) => {
evento.preventDefault();
console.log('Nome enviado:', nome);
};
return (
<form onSubmit={handleSubmit}>
<label>
Nome:
<input type="text" value={nome} onChange={handleChange} />
</label>
<button type="submit">Enviar</button>
</form>
);
}
2.3. Sincronizando múltiplos campos e lidando com diferentes tipos de input
import { useState } from 'react';
function FormularioMultiplosCampos() {
const [formData, setFormData] = useState({
nome: '',
email: '',
newsletter: false,
pais: ''
});
const handleChange = (evento) => {
const { name, value, type, checked } = evento.target;
const novoValor = type === 'checkbox' ? checked : value;
setFormData(prevState => ({
...prevState,
[name]: novoValor
}));
};
const handleSubmit = (evento) => {
evento.preventDefault();
console.log('Dados do formulário:', formData);
};
return (
<form onSubmit={handleSubmit}>
<input name="nome" value={formData.nome} onChange={handleChange} placeholder="Nome" />
<input name="email" value={formData.email} onChange={handleChange} placeholder="Email" />
<label>
<input name="newsletter" type="checkbox" checked={formData.newsletter} onChange={handleChange} />
Receber newsletter
</label>
<select name="pais" value={formData.pais} onChange={handleChange}>
<option value="">Selecione</option>
<option value="br">Brasil</option>
<option value="us">Estados Unidos</option>
</select>
<button type="submit">Enviar</button>
</form>
);
}
3. Formulários Não Controlados: Quando e Como Usar
3.1. O que são componentes não controlados e a referência ao DOM com useRef
Componentes não controlados armazenam o estado internamente no DOM. Você acessa os valores usando refs (referências) apenas quando necessário, geralmente no submit.
3.2. Implementação prática de um formulário não controlado
import { useRef } from 'react';
function FormularioNaoControlado() {
const inputNomeRef = useRef(null);
const inputEmailRef = useRef(null);
const handleSubmit = (evento) => {
evento.preventDefault();
const nome = inputNomeRef.current.value;
const email = inputEmailRef.current.value;
console.log('Dados coletados:', { nome, email });
};
return (
<form onSubmit={handleSubmit}>
<input ref={inputNomeRef} type="text" placeholder="Nome" defaultValue="" />
<input ref={inputEmailRef} type="email" placeholder="Email" defaultValue="" />
<button type="submit">Enviar</button>
</form>
);
}
3.3. Casos de uso ideais: formulários simples, integração com bibliotecas externas
Use formulários não controlados quando:
- O formulário é muito simples (apenas 1-2 campos).
- Você precisa integrar com bibliotecas jQuery ou outras que manipulam o DOM diretamente.
- O desempenho é crítico e você quer evitar re-renderizações desnecessárias.
- Você só precisa dos valores no momento do submit.
4. Comparação Detalhada: Controlado vs. Não Controlado
4.1. Performance e re-renderizações
- Controlado: Cada digitação dispara um
setStatee uma re-renderização. Para formulários grandes com muitos campos, isso pode causar lentidão. - Não controlado: O DOM gerencia tudo. Sem re-renderizações durante a digitação. Performance muito superior em formulários com centenas de campos.
4.2. Facilidade de validação e manipulação de dados em tempo real
- Controlado: Excelente para validação em tempo real (ex: mostrar erro enquanto o usuário digita).
- Não controlado: Difícil fazer validação instantânea. Você só valida no submit.
4.3. Complexidade de código e manutenção em formulários grandes
- Controlado: Código mais verboso, mas previsível e fácil de depurar. Ideal para formulários complexos com validação dinâmica.
- Não controlado: Código mais enxuto, mas pode ser difícil de manter quando a lógica de negócio cresce.
5. Validação em Formulários Controlados
5.1. Validação inline com estado e funções de validação customizadas
import { useState } from 'react';
function FormularioComValidacao() {
const [email, setEmail] = useState('');
const [erro, setErro] = useState('');
const validarEmail = (valor) => {
if (!valor.includes('@')) {
return 'Email inválido';
}
return '';
};
const handleChange = (evento) => {
const valor = evento.target.value;
setEmail(valor);
setErro(validarEmail(valor));
};
return (
<div>
<input type="email" value={email} onChange={handleChange} />
{erro && <span style={{ color: 'red' }}>{erro}</span>}
</div>
);
}
5.2. Exibição de mensagens de erro em tempo real
A validação ocorre a cada digitação, permitindo feedback imediato ao usuário.
5.3. Validação no submit e feedback visual ao usuário
const handleSubmit = (evento) => {
evento.preventDefault();
const erros = validarTodosCampos();
if (Object.keys(erros).length > 0) {
setErros(erros);
return;
}
// enviar dados
};
6. Técnicas Avançadas com Formulários Controlados
6.1. Gerenciamento de estado com useReducer para formulários complexos
import { useReducer } from 'react';
const initialState = { nome: '', email: '', erros: {} };
function reducer(state, action) {
switch (action.type) {
case 'SET_FIELD':
return { ...state, [action.field]: action.value };
case 'SET_ERRORS':
return { ...state, erros: action.erros };
default:
return state;
}
}
function FormularioComplexo() {
const [state, dispatch] = useReducer(reducer, initialState);
const handleChange = (e) => {
dispatch({ type: 'SET_FIELD', field: e.target.name, value: e.target.value });
};
return (
<form>
<input name="nome" value={state.nome} onChange={handleChange} />
</form>
);
}
6.2. Criação de hooks customizados para lógica de formulários reutilizável
function useFormulario(initialValues) {
const [values, setValues] = useState(initialValues);
const handleChange = (e) => {
const { name, value } = e.target;
setValues(prev => ({ ...prev, [name]: value }));
};
const reset = () => setValues(initialValues);
return [values, handleChange, reset];
}
// Uso:
const [dados, handleChange, reset] = useFormulario({ nome: '', email: '' });
6.3. Integração com bibliotecas de gerenciamento de estado (Zustand, Context API)
Para formulários que precisam compartilhar estado entre múltiplos componentes, use Context API ou Zustand para evitar prop drilling.
7. Boas Práticas e Padrões no Node.js + React
7.1. Envio de dados do formulário para o backend Node.js (fetch/axios)
const handleSubmit = async (evento) => {
evento.preventDefault();
try {
const response = await fetch('/api/usuarios', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
const data = await response.json();
console.log('Resposta do servidor:', data);
} catch (erro) {
console.error('Erro ao enviar:', erro);
}
};
7.2. Tratamento de respostas assíncronas e feedback de loading
const [loading, setLoading] = useState(false);
const [erroServidor, setErroServidor] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setErroServidor('');
try {
// requisição
} catch (erro) {
setErroServidor('Falha ao enviar. Tente novamente.');
} finally {
setLoading(false);
}
};
7.3. Estratégias para formulários com upload de arquivos e dados mistos
Use FormData para enviar arquivos junto com dados textuais:
const formData = new FormData();
formData.append('nome', dados.nome);
formData.append('avatar', arquivoSelecionado);
await fetch('/api/upload', {
method: 'POST',
body: formData
});
8. Conclusão e Recomendações Finais
8.1. Quando optar por formulários controlados vs. não controlados
- Controlados: Use para a maioria dos formulários em aplicações React. Preferíveis quando você precisa de validação em tempo real, formulários dinâmicos, ou quando o estado do formulário precisa ser compartilhado entre componentes.
- Não controlados: Use para formulários extremamente simples, quando a performance é prioridade máxima, ou quando integrando com código legado que manipula o DOM diretamente.
8.2. Checklist de decisão baseada no tamanho e complexidade do projeto
| Critério | Controlado | Não Controlado |
|---|---|---|
| Validação em tempo real | ✅ Excelente | ❌ Difícil |
| Performance com muitos campos | ⚠️ Pode ser lento | ✅ Rápido |
| Facilidade de manutenção | ✅ Alta | ⚠️ Média |
| Código inicial | Mais verboso | Mais enxuto |
| Integração com bibliotecas externas | ⚠️ Pode conflitar | ✅ Fácil |
8.3. Referências para aprofundamento
Para formulários muito complexos, considere bibliotecas como React Hook Form (que combina o melhor dos dois mundos) ou Formik.
Referências
- React Documentation: Forms — Documentação oficial do React sobre inputs e formulários controlados.
- React Documentation: useRef — Guia oficial sobre o hook useRef para componentes não controlados.
- React Hook Form: Getting Started — Biblioteca moderna que combina performance de formulários não controlados com API de controlados.
- Formik: Overview — Biblioteca popular para formulários controlados no React com validação integrada.
- DigitalOcean: Controlled vs Uncontrolled Components in React — Tutorial prático comparando as duas abordagens com exemplos.
- freeCodeCamp: React Forms - Controlled vs Uncontrolled Components — Guia completo com exemplos detalhados e casos de uso.
- MDN Web Docs: Sending Form Data — Documentação sobre envio de formulários para o backend, essencial para integração com Node.js.