Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
// Controlado - o pai é o proprietário do estado
function ControlledInput() {
const [value, setValue] = useState("");
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
}
// Não Controlado - o DOM é o proprietário do estado
function UncontrolledInput() {
const ref = useRef<HTMLInputElement>(null);
const handleSubmit = () => console.log(ref.current?.value);
return <input ref={ref} defaultValue="" />;
}
// Flexível - suporta ambos os modos
function FlexibleInput({
value: controlledValue,
defaultValue = "",
onChange,
}: {
value?: string;
defaultValue?: string;
onChange?: (value: string) => void;
}) {
const [internalValue, setInternalValue] = useState(defaultValue);
const isControlled = controlledValue !== undefined;
const value = isControlled ? controlledValue : internalValue;
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (!isControlled) setInternalValue(e.target.value);
onChange?.(e.target.value);
};
return <input value={value} onChange={handleChange} />;
}Quando usar isso: Todo componente interativo deve decidir quem é o proprietário de seu estado. Use controlado quando o pai precisar ler ou modificar o valor. Use não controlado para casos mais simples onde o componente se gerencia. Construa componentes de biblioteca para suportar ambos.
import { useState, useRef, useCallback, type ReactNode } from "react";
// Um componente Toggle que suporta uso controlado e não controlado
interface ToggleProps {
pressed?: boolean;
defaultPressed?: boolean;
onPressedChange?: (pressed: boolean) => void;
children: ReactNode;
}
function Toggle({
pressed: controlledPressed,
defaultPressed = false,
onPressedChange,
children,
}: ToggleProps) {
const [internalPressed, setInternalPressed] = useState(defaultPressed);
const isControlled = controlledPressed !== undefined;
const pressed = isControlled ? controlledPressed : internalPressed;
const handleClick = useCallback(() => {
const next = !pressed;
if (!isControlled) {
setInternalPressed(next);
}
onPressedChange?.(next);
}, [pressed, isControlled, onPressedChange]);
return (
<button
type="button"
role="switch"
aria-checked={pressed}
onClick={handleClick}
className={`px-4 py-2 rounded-full transition-colors ${
pressed
? "bg-blue-600 text-white"
: "bg-gray-200 text-gray-700"
}`}
>
{children}
</button>
);
}
// --- Exemplos de uso ---
// Não controlado - o componente gerencia seu próprio estado
function SimpleToggle() {
return (
<Toggle
defaultPressed={false}
onPressedChange={(p) => console.log("Toggled:", p)}
>
Modo Escuro
</Toggle>
);
}
// Controlado - o pai possui e pode substituir o estado
function SyncedToggles() {
const [enabled, setEnabled] = useState(false);
return (
<div className="flex gap-4">
<Toggle pressed={enabled} onPressedChange={setEnabled}>
Toggle A
</Toggle>
<Toggle pressed={enabled} onPressedChange={setEnabled}>
Toggle B
</Toggle>
<p>Ambos estão: {enabled ? "LIGADOS" : "DESLIGADOS"}</p>
</div>
);
}O que isso demonstra:
isControlled determina qual fonte de estado usar.defaultPressed para o estado inicial.default* (ex: defaultValue, defaultChecked, defaultPressed) sinaliza o estado inicial não controlado.undefined.useActionState podem simplificar padrões de formulário, mas a distinção controlado/não controlado ainda se aplica.| Convenção de Prop | Modo | Propósito |
|---|---|---|
value | Controlado | Valor atual, definido pelo pai |
defaultValue | Não Controlado | Valor inicial, o componente gerencia depois |
onChange | Ambos | Callback notificando o pai sobre mudanças |
ref | Não Controlado | Acesso imperativo para ler o valor do DOM |
Hook useControllableState - extraia o padrão para um hook reutilizável:
function useControllableState<T>({
value: controlledValue,
defaultValue,
onChange,
}: {
value?: T;
defaultValue: T;
onChange?: (value: T) => void;
}): [T, (next: T) => void] {
const [internalValue, setInternalValue] = useState(defaultValue);
const isControlled = controlledValue !== undefined;
const value = isControlled ? controlledValue : internalValue;
const setValue = useCallback(
(next: T) => {
if (!isControlled) setInternalValue(next);
onChange?.(next);
},
[isControlled, onChange]
);
return [value, setValue];
}
// Uso dentro de qualquer componente
function Slider({ value, defaultValue = 0, onChange, min = 0, max = 100 }: SliderProps) {
const [current, setCurrent] = useControllableState({
value,
defaultValue,
onChange,
});
// ... renderiza com `current` e `setCurrent`
}Ações de formulário do React 19 - formulários não controlados com ações de servidor:
function ContactForm() {
async function submitAction(formData: FormData) {
"use server";
const email = formData.get("email") as string;
await sendEmail(email);
}
return (
<form action={submitAction}>
<input name="email" type="email" defaultValue="" />
<button type="submit">Enviar</button>
</form>
);
}value?: T) para a prop controlada para que undefined sinalize o modo não controlado.defaultValue obrigatório quando value não for fornecido, ou dê a ele um padrão razoável.Alternar entre controlado e não controlado - Mudar value de undefined para um valor definido (ou vice-versa) durante o ciclo de vida do componente causa bugs. O React avisa sobre isso. Correção: Decida o modo no momento da montagem e mantenha-o. Use um ref para rastrear o modo inicial.
Input controlado com atualização de estado atrasada - Se o manipulador onChange atualizar o estado de forma assíncrona (por exemplo, com debounce), o input parecerá congelado. Correção: Atualize o estado local imediatamente e adie apenas o efeito colateral, não a atualização do estado.
onChange ausente em componente controlado - Fornecer value sem onChange cria um input somente leitura. O React avisa. Correção: Sempre combine value com onChange, ou use readOnly se for intencional.
defaultValue mudando após a montagem - Mudar defaultValue após a primeira renderização não tem efeito. Correção: Use uma prop key para remontar o componente se o valor inicial precisar ser redefinido.
| Abordagem | Compromisso |
|---|---|
| Controlado | Controle total do pai; requer gerenciamento de estado no pai |
| Não Controlado | Mais simples; mais difícil para o pai ler ou sincronizar o estado |
| Flexível (ambos os modos) | Melhor para bibliotecas; maior complexidade de implementação |
| Ações de formulário do React 19 | Ótimo para formulários; não controlado com manipulação do lado do servidor |
| Biblioteca de gerenciamento de estado | Zustand ou Redux podem atuar como o controlador para formulários complexos |
value + onChange). O pai é a única fonte de verdade.ref ou receber notificações via callbacks.default* sinaliza o estado inicial não controlado.const isControlled = controlledValue !== undefined;
const value = isControlled ? controlledValue : internalValue;value) é undefined.undefined, use o estado interno.[value, setValue] e lida com o estado interno, passagem controlada e callbacks onChange.<form action={submitAction}>
<input name="email" type="email" defaultValue="" />
<button type="submit">Enviar</button>
</form>defaultValue e leem valores do FormData.value de undefined para um valor definido (ou vice-versa) causa bugs e avisos do React.onChange atualizar o estado de forma assíncrona (por exemplo, com debounce), a prop value do input não é atualizada imediatamente.interface ToggleProps {
pressed?: boolean; // controlado
defaultPressed?: boolean; // não controlado
onPressedChange?: (pressed: boolean) => void;
children: ReactNode;
}pressed?: boolean) para que undefined sinalize o modo não controlado.defaultPressed um valor padrão razoável.value quanto defaultValue.defaultValue apenas define o estado inicial na primeira renderização.defaultValue em renderizações subsequentes porque o estado interno já foi inicializado.key para remontar o componente se o valor inicial precisar ser redefinido.value em cada renderização.onChange ausente.value com onChange, ou adicione explicitamente o atributo readOnly se for intencional.Revisado por Chris St. John·Última atualização: 16 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥