Controlled vs Uncontrolled
Uma decisão importante que afeta bastante na qualidade do seu código é determinar onde o estado de um componente deve residir, e esta decisão impacta diretamente a flexibilidade, reusabilidade e complexidade.
Um exemplo clássico desta decisão é a implementação de um modal: deve ele gerenciar seu próprio estado de visibilidade ou delegar este controle ao componente pai?
Componentes Controlados: Delegando o Controle
Em um componente controlado, o estado é gerenciado externamente pelo componente pai.
Esta abordagem oferece maior flexibilidade e permite que o componente pai coordene múltiplos estados relacionados.
Nessa abordagem, podemos dizer que a flexibilidade do Modal é alta, mas a
responsabilidade sobre ele também é, pois, o componente pai precisa manter a lógica necessária para abrir e fechar o modal.
// Exemplo de Modal controlado:
export function Modal({ isOpen, onChangeIsOpen, children }) {
// O componente recebe o estado como prop e delega mudanças através de callbacks
return (
<dialog
open={isOpen}
onClose={() => onChangeIsOpen(false)}
>
{children}
</dialog>
);
}
// Exemplo de uso:
export function App() {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<Modal isOpen={isOpen} onChangeIsOpen={setIsOpen}>
<p>Greetings, one and all!</p>
<button onClick={() => setIsOpen(false)}>OK</button>
</Modal>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
</div>
);
}
Componentes Não Controlados: Autonomia Interna
Por outro lado, componentes não controlados mantêm e gerenciam seu próprio estado internamente. Esta abordagem reduz a complexidade do componente pai e é ideal quando o comportamento do componente pode ser encapsulado de forma independente.
// Exemplo de modal não controlado:
export function Modal({ children, onOpenChange }) {
const [isOpen, setIsOpen] = useState(false);
const handleOpenChange = (newState) => {
setIsOpen(newState);
onOpenChange?.(newState); // Opcional: notifica o pai sobre mudanças
};
if (!isOpen) {
return (
<button onClick={() => handleOpenChange(true)}>Open Modal</button>
);
}
return (
<dialog
open
onClose={() => handleOpenChange(false)}
>
{children}
<button onClick={() => handleOpenChange(false)}>OK</button>
</dialog>
);
}
// App.tsx
function App() {
return (
<Modal onOpenChange={(isOpen) => console.log('Modal state:', isOpen)}>
<p>Greetings, one and all!</p>
</Modal>
);
}
Esta decisão de design influencia diretamente a manutenibilidade e reusabilidade do seu código.
Use componentes controlados quando:
- O componente pai precisa coordenar múltiplos estados relacionados
- Você precisa de controle preciso sobre o comportamento do componente
- O estado do componente precisa ser sincronizado com outras partes da aplicação
Use componentes não controlados quando:
- O comportamento do componente é independente do resto da aplicação
- Você quer reduzir a complexidade do componente pai
- O componente pode funcionar de forma autônoma
Top comments (0)