Inputs Controlados vs. Não Controlados
No React 19, você não precisa escrever inputs totalmente controlados manualmente para a maioria dos campos nativos. Mas padrões controlados (especialmente via Controller) ainda são recomendados ao trabalhar com bibliotecas de UI modernas ou formulários interativos complexos.
React Hook Form adota componentes não controlados, mas é totalmente compatível com componentes controlados.
A maioria das bibliotecas de UI é construída apenas para o modo controlado, então a mistura é a maneira padrão de obter o melhor dos dois mundos:
Desempenho de campos nativos não controlados
Flexibilidade de componentes de UI controlados
Controlado - O estado do React é a única fonte de verdade. Cada pressionamento de tecla atualiza o estado e re-renderiza.
function ControlledInput () {
const [ email , setEmail ] = useState ( "" );
return (
< input
type = "email"
value = {email}
onChange = {( e ) => setEmail (e.target.value)}
/>
);
}
Não Controlado - O DOM armazena o valor. Você o lê no submit (via ref ou FormData).
function UncontrolledInput () {
const ref = useRef < HTMLInputElement >( null );
function handleSubmit ( e : React . FormEvent ) {
e. preventDefault ();
console. log (ref.current?.value);
}
return (
< form onSubmit = {handleSubmit}>
< input type = "email" ref = {ref} defaultValue = "" />
< button type = "submit" >Submit</ button >
</ form >
);
}
O padrão moderno para formulários simples - sem estado, sem refs, sem re-renderizações.
async function subscribe ( formData : FormData ) {
"use server" ;
const email = formData. get ( "email" );
await saveSubscriber (email);
}
export function NewsletterForm () {
return (
< form action = {subscribe}>
< input name = "email" type = "email" required />
< button type = "submit" >Subscribe</ button >
</ form >
);
}
register() conecta inputs como não controlados - rápido, re-renderizações mínimas.
import { useForm } from "react-hook-form" ;
type FormValues = { email : string ; password : string };
export function LoginForm () {
const { register , handleSubmit } = useForm < FormValues >();
return (
< form onSubmit = { handleSubmit (( data ) => console. log (data))}>
< input { ... register ( "email" )} type = "email" />
< input { ... register ( "password" )} type = "password" />
< button type = "submit" >Log in</ button >
</ form >
);
}
Use Controller para conectar a componentes controlados como Select do shadcn/ui ou MUI.
import { Controller, useForm } from "react-hook-form" ;
import { Select, SelectTrigger, SelectContent, SelectItem } from "@/components/ui/select" ;
export function PlanForm () {
const { control , handleSubmit } = useForm ({ defaultValues: { plan: "free" } });
return (
< form onSubmit = { handleSubmit (( data ) => console. log (data))}>
< Controller
name = "plan"
control = {control}
render = {({ field }) => (
< Select value = {field.value} onValueChange = {field.onChange}>
< SelectTrigger />
< SelectContent >
< SelectItem value = "free" >Free</ SelectItem >
< SelectItem value = "pro" >Pro</ SelectItem >
</ SelectContent >
</ Select >
)}
/>
< button type = "submit" >Save</ button >
</ form >
);
}
A maioria dos <input>, <textarea>, <select> nativos → não controlados com register() ou server actions
Componentes personalizados ou de terceiros (shadcn/ui, MUI, etc.) → controlados com <Controller>
Precisa de lógica em tempo real em alguns campos, desempenho em outros → misture conforme necessário
Nunca alterne um único input de controlado para não controlado (ou vice-versa) durante sua vida útil - isso dispara avisos do React.
Não adicione manualmente value + onChange a um campo que usa register() - isso quebra o padrão não controlado.
Para o mesmo nome de campo, mantenha uma abordagem consistente.
Sempre forneça defaultValue (não value) para inputs não controlados para evitar o aviso de controlado/não controlado.
O formulário é simples (login, contato, newsletter, configurações básicas - geralmente menos de 8 campos)
Você precisa principalmente dos valores apenas no submit
Você está usando server actions com <form action={...}>
Você deseja re-renderizações mínimas e o melhor desempenho possível
Você planeja usar useActionState e useFormStatus
A melhoria progressiva é importante (o formulário ainda deve funcionar se o JavaScript falhar)
Você está confortável em confiar principalmente na validação HTML5 mais a validação do lado do servidor
Você deseja o código mais simples com reset automático do formulário após o envio bem-sucedido
Não há atualizações complexas de UI em tempo real ou campos interdependentes
Você prefere menos boilerplate e um tamanho de bundle menor
O formulário tem muitos campos (mais de 8-10) ou é multi-etapas
Você precisa de validação em tempo real, formatação ou feedback ao vivo enquanto o usuário digita
Campos dependem uns dos outros (mostrar/ocultar, habilitar/desabilitar ou realizar cálculos)
Você precisa pré-popular o formulário com dados de uma API
Você deseja mensagens de erro instantâneas no lado do cliente com excelente UX
Você tem lógica complexa no lado do cliente antes do envio (força da senha, totais, pré-visualizações ao vivo)
Você está usando TypeScript e deseja forte segurança de tipo em todo o formulário
Você precisa de controle granular sobre as atualizações da UI
O formulário inclui upload de arquivos com pré-visualização ou indicadores de progresso
Você deseja manter a maior parte da lógica no cliente antes de enviar os dados para o servidor
Formulário simples + Server Actions → Inputs não controlados com useActionState e useFormStatus
Complexo, multi-etapas ou UX rica → react-hook-form + zod (ainda usa inputs não controlados internamente para desempenho)
Edição de dados existentes → Geralmente react-hook-form com reset() ou requestFormReset nativo
Desempenho máximo e código mínimo → Inputs não controlados nativos com hooks do React 19
Interatividade intensa em tempo real → react-hook-form para o melhor equilíbrio entre controle e desempenho
Comece com inputs não controlados + hooks nativos do React 19 para qualquer formulário novo, simples ou de tamanho médio.
Mude para react-hook-form no momento em que precisar de validação em tempo real, campos condicionais ou lógica complexa no lado do cliente.
Mesmo ao usar react-hook-form, você ainda se beneficia do desempenho dos inputs não controlados nos bastidores.