Um componente controlado é um elemento de formulário cujo valor mora no estado do React: você passa value e atualiza o estado no onChange, então é o React que decide o que o input mostra. Um componente não controlado guarda o valor no DOM, como no HTML puro: você passa um defaultValue inicial e lê o valor atual depois por uma ref ou com FormData.
Digite nos dois inputs. O parágrafo acompanha o primeiro a cada tecla, porque cada mudança define o estado e renderiza de novo. O segundo input muda só no DOM; o React não renderiza, e você só vê o valor dele quando "Read both" o pede.
Inputs controlados
Com um input controlado, o estado do React é a única fonte da verdade. O fluxo a cada tecla é:
- O usuário digita, e o navegador dispara um evento de input.
- O seu handler de
onChangelêe.target.valuee chama o setter. - O React renderiza de novo e escreve o novo estado de volta no
valuedo input.
Como o seu código fica no meio desse ciclo, você pode fazer coisas que um input não controlado não consegue: rejeitar ou remodelar caracteres, mostrar o valor em outro lugar, desabilitar um botão até o valor ser válido ou definir o valor pelo código (um botão "Clear", uma sugestão que o usuário clica).
Tente digitar letras: elas nunca aparecem, porque o handler remove tudo o que não é dígito antes de definir o estado. O botão Verify fica desabilitado até haver exatamente seis dígitos.
Controlado é o que os exemplos de formulário da página de formulários usam, e é a escolha padrão na maior parte do código React.
Inputs não controlados
Um input não controlado funciona como HTML puro. O React o renderiza uma vez com defaultValue (ou defaultChecked para checkboxes e rádios) e depois o deixa em paz. Para ler o valor, guarde uma ref para o elemento ou leia todos os campos com nome do formulário no envio, com FormData.
Não há estado para os campos nem onChange. Os valores só são coletados quando o formulário é enviado. Desmarque o checkbox e envie de novo: a chave subscribe desaparece, porque um checkbox desmarcado simplesmente não faz parte do FormData.
Mudar o defaultValue depois da primeira renderização não faz nada no input, já que o React só o aplica quando o elemento é criado. Para reiniciar um formulário não controlado, chame e.target.reset() ou dê ao formulário uma nova key para que o React crie inputs novos.
Um input em que não dá para digitar
Se você passa value mas nenhum onChange, o input é controlado, mas nada nunca atualiza o estado. O React escreve o mesmo valor de volta depois de cada tecla, então o input parece congelado.
Tente digitar em cada campo. O primeiro não muda. O segundo é a correção para um input controlado, e enquanto você digita nele, o primeiro e o último campos o acompanham, porque os três leem o mesmo estado. O terceiro aceita digitação porque é não controlado. O quarto diz de propósito que não pode ser editado.
Em desenvolvimento, o React registra um aviso para o primeiro input: "You provided a value prop to a form field without an onChange handler". A prévia roda como um build de produção, então você não vai ver esse aviso aqui, mas vai vê-lo no seu próprio servidor de desenvolvimento.
Mudando de não controlado para controlado
O React decide se um input é controlado olhando para value. Se value é undefined ou null, o input é não controlado. Então um input cujo value começa como undefined e depois vira uma string passa de não controlado para controlado ao longo da vida, e o React avisa em desenvolvimento: "A component is changing an uncontrolled input to be controlled".
O aviso aponta para um bug real. Voltar no sentido contrário, de uma string para undefined, torna o input não controlado de novo, e o React para de escrever nele.
Digite algo nos dois inputs e aperte cada botão Clear. O segundo input fica vazio. O primeiro mantém o texto: definir o estado como undefined o tornou não controlado, então o React não controla mais o que ele mostra.
A origem comum do undefined são dados que ainda não carregaram (value={user.name} antes de o usuário chegar) ou um campo de objeto que ainda não existe. Duas correções:
const [name, setName] = useState(''); // start with a string
<input value={user?.name ?? ''} /> // or fall back to one
Para checkboxes, a mesma regra vale para checked: comece com false, não undefined.
Inputs de arquivo são sempre não controlados
Um <input type="file"> não pode ser controlado, porque os navegadores não deixam uma página definir o valor dele (uma página que pudesse escolher arquivos do seu disco seria uma falha de segurança). Leia os arquivos escolhidos em e.target.files no onChange, ou por uma ref quando o formulário for enviado.
Escolha um ou dois arquivos do seu computador; nada é enviado, o exemplo só lista nomes e tamanhos. O único valor que você pode definir em um input de arquivo é a string vazia, e é assim que o botão Clear o reinicia.
Quando escolher cada um
| Controlado | Não controlado | |
|---|---|---|
| O valor mora no | Estado do React | DOM |
| Definir o valor inicial | value / checked | defaultValue / defaultChecked |
| Ler o valor | A qualquer momento, no estado | Com uma ref, ou FormData no envio |
| Renderiza a cada tecla | Sim | Não |
| Mudar o valor pelo código | Definir o estado | Escrever no DOM por uma ref |
| Bom para | Validação ao vivo, formatação, campos dependentes | Formulários simples lidos no envio, inputs de arquivo, form actions |
Escolha controlado quando a tela precisa reagir ao valor enquanto o usuário digita. Escolha não controlado quando você só precisa dos valores no final, que também é como funcionam as form actions do React 19: <form action={fn}> entrega à sua função um FormData e reinicia os campos não controlados depois.
Renderizar a cada tecla parece caro, mas para um formulário normal não é problema. Só vira problema quando uma árvore grande e lenta renderiza de novo junto com o input; aí mantenha o estado perto do input ou mova a parte lenta para um componente próprio.
As mesmas duas palavras descrevem os seus próprios componentes. Um componente que recebe o valor e um onChange do pai pelas props é controlado pelo pai; um que guarda o próprio estado é não controlado. Mover o estado para cima para tornar um componente controlado é o assunto de elevar o estado.
Perguntas frequentes
O que é um componente controlado no React?
Um elemento de formulário cujo valor atual vem do estado do React, definido com value (ou checked) e atualizado pelo onChange. O estado do React é a única fonte da verdade, então você sempre sabe o valor e pode mudá-lo pelo código.
O que é um componente não controlado no React?
Um elemento de formulário que guarda o próprio valor no DOM, como no HTML puro. Você dá a ele um valor inicial com defaultValue (ou defaultChecked) e lê o valor atual quando precisa, por uma ref ou com FormData no envio.
Por que não consigo digitar no meu input React?
Você passou value sem um onChange. O input é controlado, então o React continua recolocando o valor do estado depois de cada tecla. Adicione um onChange que atualize o estado, troque para defaultValue ou adicione readOnly se ele realmente não deve mudar.
Como corrijo "A component is changing an uncontrolled input to be controlled"?
O value do input começou como undefined (ou null) e depois virou uma string. Garanta que ele seja sempre uma string: comece o estado com '' ou escreva value={name ?? ''}.
Devo usar inputs controlados ou não controlados?
Use inputs controlados quando a UI reage ao valor enquanto o usuário digita: validação, formatação, um botão desabilitado, campos que dependem uns dos outros. Use inputs não controlados quando você só precisa dos valores no envio, e para inputs de arquivo, que são sempre não controlados.