<h2>O Que São Componentes Controlados e Não Controlados?</h2>
<p>Antes de mais nada, precisamos entender que essa distinção existe principalmente no contexto do React, embora o conceito seja aplicável a outros frameworks. Um <strong>componente controlado</strong> é aquele cujo estado é gerenciado pelo React — ou seja, o valor do elemento de formulário vem do estado da aplicação e é atualizado através de event handlers. Um <strong>componente não controlado</strong> funciona de forma similar aos elementos HTML tradicionais, onde o DOM é a fonte da verdade, e você acessa o valor quando necessário, geralmente através de refs.</p>
<p>Essa diferença fundamental afeta como você projeta seus formulários, gerencia dados e implementa validações. Escolher a abordagem correta desde o início evita refatorações desnecessárias e problemas de sincronização de dados. Não é uma questão de qual é "melhor", mas qual é mais apropriada para cada situação específica.</p>
<h2>Componentes Controlados: Dominando Seu Formulário</h2>
<p>Um componente controlado coloca o React no centro do controle. Todo valor do input é sincronizado com o estado, e qualquer mudança passa por um handler que atualiza esse estado. Dessa forma, React conhece sempre qual é o valor atual.</p>
<h3>Estrutura Básica</h3>
<pre><code class="language-jsx">import React, { useState } from 'react';
function FormularioControlado() {
const [email, setEmail] = useState('');
const [senha, setSenha] = useState('');
const handleEmailChange = (event) => {
setEmail(event.target.value);
};
const handleSenhaChange = (event) => {
setSenha(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
console.log('Email:', email, 'Senha:', senha);
// Enviar dados para servidor
};
return (
<form onSubmit={handleSubmit}>
<label>
Email:
<input
type="email"
value={email}
onChange={handleEmailChange}
/>
</label>
<label>
Senha:
<input
type="password"
value={senha}
onChange={handleSenhaChange}
/>
</label>
<button type="submit">Enviar</button>
</form>
);
}
export default FormularioControlado;</code></pre>
<p>Note que cada input possui um atributo <code>value</code> vinculado ao estado e um <code>onChange</code> que atualiza esse estado. React é responsável por manter esses inputs sempre sincronizados com a aplicação. Você pode validar, desabilitar ou modificar campos em tempo real porque sempre tem acesso ao valor atual.</p>
<h3>Validação em Tempo Real</h3>
<p>Um dos grandes benefícios dos componentes controlados é implementar feedback imediato ao usuário:</p>
<pre><code class="language-jsx">import React, { useState } from 'react';
function FormularioComValidacao() {
const [username, setUsername] = useState('');
const [erro, setErro] = useState('');
const handleChange = (event) => {
const valor = event.target.value;
setUsername(valor);
// Validação em tempo real
if (valor.length < 3) {
setErro('Usuário deve ter no mínimo 3 caracteres');
} else if (!/^[a-zA-Z0-9_]+$/.test(valor)) {
setErro('Usuário pode conter apenas letras, números e underscore');
} else {
setErro('');
}
};
return (
<div>
<input
type="text"
value={username}
onChange={handleChange}
placeholder="Digite seu usuário"
/>
{erro && <p style={{ color: 'red' }}>{erro}</p>}
<p>Caracteres digitados: {username.length}</p>
</div>
);
}
export default FormularioComValidacao;</code></pre>
<p>Aqui você vê claramente o poder: a validação ocorre a cada mudança, e você pode renderizar mensagens de erro ou desabilitar o botão de submit dinamicamente, tudo porque React mantém controle total sobre o estado.</p>
<h2>Componentes Não Controlados: Simplicidade com Refs</h2>
<p>Componentes não controlados deixam o DOM ser a fonte da verdade. Você não vincula um valor ao estado do React — em vez disso, acessa o valor diretamente do DOM quando necessário, usualmente com refs. Essa abordagem é mais próxima de como HTML tradicional funciona.</p>
<h3>Estrutura Básica com useRef</h3>
<pre><code class="language-jsx">import React, { useRef } from 'react';
function FormularioNaoControlado() {
const emailRef = useRef(null);
const senhaRef = useRef(null);
const handleSubmit = (event) => {
event.preventDefault();
const email = emailRef.current.value;
const senha = senhaRef.current.value;
console.log('Email:', email, 'Senha:', senha);
// Enviar dados para servidor
};
return (
<form onSubmit={handleSubmit}>
<label>
Email:
<input type="email" ref={emailRef} />
</label>
<label>
Senha:
<input type="password" ref={senhaRef} />
</label>
<button type="submit">Enviar</button>
</form>
);
}
export default FormularioNaoControlado;</code></pre>
<p>Não há <code>value</code> nem <code>onChange</code>. Os inputs existem independentemente do estado React. Você acessa o valor apenas quando realmente precisa dele — no exemplo acima, no momento do submit. Isso é simples e direto para formulários básicos.</p>
<h3>Resetar Formulário com Refs</h3>
<p>Uma vantagem prática dos componentes não controlados é resetar formulários de forma simples:</p>
<pre><code class="language-jsx">import React, { useRef } from 'react';
function FormularioComReset() {
const formRef = useRef(null);
const handleSubmit = (event) => {
event.preventDefault();
console.log('Formulário enviado');
// Limpar formulário
formRef.current.reset();
};
return (
<form ref={formRef} onSubmit={handleSubmit}>
<input type="text" placeholder="Nome" />
<input type="email" placeholder="Email" />
<textarea placeholder="Mensagem"></textarea>
<button type="submit">Enviar</button>
</form>
);
}
export default FormularioComReset;</code></pre>
<p>O <code>reset()</code> nativo do formulário HTML limpa todos os campos automaticamente — sem você ter que respetar múltiplas variáveis de estado.</p>
<h2>Comparação Prática: Quando Usar Cada Abordagem</h2>
<p>A escolha entre controlado e não controlado depende dos requisitos específicos do seu formulário. Não existe uma resposta universal, mas há padrões claros que ajudam nessa decisão.</p>
<h3>Use Componentes Controlados Para</h3>
<p><strong>Validação em tempo real</strong>: Quando você precisa avisar o usuário sobre erros enquanto ele digita.</p>
<p><strong>Desabilitar campo dinamicamente</strong>: Botão de submit desabilitado até que o formulário seja válido.</p>
<p><strong>Integração com lógica complexa</strong>: Quando o valor de um campo afeta outros campos (ex: seleção de estado carrega cidades).</p>
<p><strong>Histórico e desfazer</strong>: Quando você precisa manter histórico de mudanças para implementar undo/redo.</p>
<p><strong>Exemplo prático de integração entre campos:</strong></p>
<pre><code class="language-jsx">import React, { useState } from 'react';
function FormularioIntegrado() {
const [pais, setPais] = useState('');
const [estado, setEstado] = useState('');
const estados = {
'brasil': ['São Paulo', 'Rio de Janeiro', 'Minas Gerais'],
'argentina': ['Buenos Aires', 'Córdoba', 'Rosario'],
};
const handlePaisChange = (event) => {
const novoPais = event.target.value;
setPais(novoPais);
setEstado(''); // Reseta estado quando país muda
};
return (
<div>
<select value={pais} onChange={handlePaisChange}>
<option value="">Selecione um país</option>
<option value="brasil">Brasil</option>
<option value="argentina">Argentina</option>
</select>
{pais && (
<select value={estado} onChange={(e) => setEstado(e.target.value)}>
<option value="">Selecione um estado</option>
{estados[pais]?.map((est) => (
<option key={est} value={est}>
{est}
</option>
))}
</select>
)}
<p>
{pais && estado
? Você selecionou ${estado}, ${pais}
: 'Selecione país e estado'}
</p>
</div>
);
}
export default FormularioIntegrado;</code></pre>
<p>Aqui, um campo controlado afeta diretamente o outro. Isso é praticamente impossível com componentes não controlados sem lógica adicional.</p>
<h3>Use Componentes Não Controlados Para</h3>
<p><strong>Integração com código legacy</strong>: Quando você está migrando gradualmente de jQuery ou código vanilla JavaScript.</p>
<p><strong>Formulários muito simples</strong>: Inputs básicos onde você coleta dados apenas no submit.</p>
<p><strong>Upload de arquivos</strong>: O <code><input type="file"></code> é inerentemente não controlado em React.</p>
<p><strong>Performance com formulários gigantes</strong>: Formulários com centenas de campos onde cada atualização de estado causaria re-renders.</p>
<p><strong>Exemplo com upload de arquivo:</strong></p>
<pre><code class="language-jsx">import React, { useRef } from 'react';
function UploadArquivo() {
const arquivoRef = useRef(null);
const handleSubmit = (event) => {
event.preventDefault();
const arquivo = arquivoRef.current.files[0];
if (arquivo) {
console.log('Arquivo selecionado:', arquivo.name);
// Fazer upload
}
};
return (
<form onSubmit={handleSubmit}>
<input type="file" ref={arquivoRef} />
<button type="submit">Upload</button>
</form>
);
}
export default UploadArquivo;</code></pre>
<p>Não faz sentido tentar controlar <code><input type="file"></code> — o React não pode setar o valor por questões de segurança. Refs são a solução natural.</p>
<h2>Abordagem Híbrida: O Melhor dos Dois Mundos</h2>
<p>Na prática, projetos reais frequentemente usam uma mistura de ambas. Você pode ter um formulário com campos controlados para validação complexa e campos não controlados para dados simples.</p>
<pre><code class="language-jsx">import React, { useState, useRef } from 'react';
function FormularioHibrido() {
// Controlado: email com validação
const [email, setEmail] = useState('');
const [erroEmail, setErroEmail] = useState('');
// Não controlado: arquivo
const arquivoRef = useRef(null);
// Não controlado: campo simples
const nomeRef = useRef(null);
const handleEmailChange = (event) => {
const valor = event.target.value;
setEmail(valor);
if (!valor.includes('@')) {
setErroEmail('Email inválido');
} else {
setErroEmail('');
}
};
const handleSubmit = (event) => {
event.preventDefault();
const dados = {
email: email,
nome: nomeRef.current.value,
arquivo: arquivoRef.current.files[0],
};
console.log('Enviando:', dados);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>
Nome (não controlado):
<input type="text" ref={nomeRef} />
</label>
</div>
<div>
<label>
Email (controlado):
<input
type="email"
value={email}
onChange={handleEmailChange}
/>
</label>
{erroEmail && <p style={{ color: 'red' }}>{erroEmail}</p>}
</div>
<div>
<label>
Arquivo (não controlado):
<input type="file" ref={arquivoRef} />
</label>
</div>
<button type="submit" disabled={erroEmail !== ''}>
Enviar
</button>
</form>
);
}
export default FormularioHibrido;</code></pre>
<p>Essa abordagem pragmática reconhece que diferentes partes de um formulário têm diferentes necessidades. Email precisa de validação em tempo real (controlado), nome é simples (não controlado), e arquivo não pode ser controlado mesmo que quiséssemos.</p>
<h2>Conclusão</h2>
<p>Aprendemos que <strong>componentes controlados</strong> colocam React no controle total do estado do formulário, permitindo validação em tempo real, lógica interdependente entre campos e controle fino sobre o que o usuário vê. São a escolha padrão para formulários complexos e interativos. <strong>Componentes não controlados</strong> delegam o controle ao DOM, acessando valores apenas quando necessário através de refs — são simples, diretos e adequados para formulários básicos, uploads e situações onde performance é crítica. A maioria dos projetos reais usa uma <strong>abordagem híbrida</strong>, combinando ambas conforme a necessidade de cada campo. A chave é reconhecer que essa não é uma questão de "um é melhor que o outro", mas de escolher a ferramenta certa para o trabalho específico.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://react.dev/reference/react-dom/components/input#controlling-an-input-with-a-state-variable" target="_blank" rel="noopener noreferrer">React Documentation: Forms - Controlled Components</a></li>
<li><a href="https://react.dev/learn/sharing-state-between-components#controlled-and-uncontrolled-components" target="_blank" rel="noopener noreferrer">React Documentation: Uncontrolled Components</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Learn/Forms" target="_blank" rel="noopener noreferrer">MDN Web Docs: HTML Forms</a></li>
<li><a href="https://formik.org/" target="_blank" rel="noopener noreferrer">Formik Documentation: Working with Forms</a></li>
<li><a href="https://react.dev/reference/react/useRef" target="_blank" rel="noopener noreferrer">React Hooks Documentation: useRef</a></li>
</ul>
<p><!-- FIM --></p>