React: estratégias para componentes escaláveis
Salve o/
Se você já perdeu tempo debugando aquele componente que parecia simples, mas tinha 300 linhas e fazia absolutamente tudo, este artigo é pra você.
Componentes mal projetados não só dificultam a vida de quem desenvolve hoje, mas também de quem vai dar manutenção no futuro – e, spoiler, pode ser você mesmo.
Aqui, vamos explorar os erros mais comuns em componentes React e como transformá-los em exemplos de boa prática.
Além disso, vamos ver como desacoplar responsabilidades e aplicar estratégias para escalar seu código de forma otimizada. Bora?
O problema do componente “faz-tudo”
Sabe aquele componente chamado UserProfile que, além de renderizar a interface, faz requisições, valida formulários, autentica o usuário e ainda tenta salvar o mundo?
Pois é, esse é um antipadrão clássico que chamamos de componente “faz-tudo”.
Esses componentes acabam sendo grandes, difíceis de testar, e praticamente impossíveis de reutilizar. Alguma coisa assim:
function UserProfile() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('/api/user')
.then((res) => res.json())
.then(setUser)
.catch(setError)
.finally(() => setLoading(false));
}, []);
function handleSubmit(event) {
event.preventDefault();
// Lógica de validação e envio de formulário
}
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<form onSubmit={handleSubmit}>
<h1>{user.name}</h1>
<input type="text" name="email" defaultValue={user.email} />
<button type="submit">Update</button>
</form>
);
}
Esse componente faz tudo ao mesmo tempo:
- Busca os dados do usuário.
- Gerencia o estado de loading e erro.
- Renderiza o formulário.
- Lida com o envio e validação.
Estratégias para desacoplar responsabilidades
Vamos quebrar esse monstro em pedaços menores, organizando cada responsabilidade em um lugar apropriado.
1. Encapsular lógica de dados em hooks customizados
Ao mover a lógica de busca para um hook customizado, o componente fica mais limpo e focado apenas na renderização.
function useUser() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('/api/user')
.then((res) => res.json())
.then(setUser)
.catch(setError)
.finally(() => setLoading(false));
}, []);
return { user, loading, error };
}
Agora, UserProfile apenas consome o hook:
function UserProfile() {
const { user, loading, error } = useUser();
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
<h1>{user.name}</h1>
<p>Email: {user.email}</p>
</div>
);
}
2. Separar a lógica de formulários
Outra prática comum é usar bibliotecas como react-hook-form para gerenciar formulários. Assim, você não precisa reimplementar lógica de validação sempre.
function UserForm({ user }) {
const { register, handleSubmit } = useForm({
defaultValues: user,
});
const onSubmit = (data) => {
console.log('Form submitted:', data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('email')} type="email" />
<button type="submit">Update</button>
</form>
);
}
Agora o UserProfile só orquestra os componentes:
function UserProfile() {
const { user, loading, error } = useUser();
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
<h1>{user.name}</h1>
<UserForm user={user} />
</div>
);
}
Com as melhorias, nosso código agora segue boas práticas:
- Desacoplamento: Cada componente ou hook tem uma única responsabilidade.
- Reutilização: O hook useUser pode ser usado em outras partes da aplicação.
- Facilidade de manutenção: Cada unidade de código é menor, mais clara e mais fácil de testar.
Além disso, você pode reutilizar o UserForm com outros dados ou cenários – um baita ganho de escalabilidade.
Componentes que escalam
A mágica de um componente escalável está na capacidade de mudar ou crescer sem causar um efeito dominó no resto da aplicação.
Quando você separa as responsabilidades de maneira clara, o código não só fica mais limpo, mas também se torna flexível para evoluir com as necessidades do projeto.
Pensa comigo: e se amanhã você precisar trocar a API que fornece os dados do usuário? Ou mudar a forma como o formulário valida os campos?
Se cada parte do sistema estiver isolada, você só precisa mexer onde realmente importa. Nada de abrir um arquivo gigante, cruzar os dedos e torcer pra não quebrar nada.
1. Foque em uma tarefa por componente
É tentador deixar um componente cuidar de várias coisas ao mesmo tempo. Afinal, funciona, né? Só que essa abordagem vira uma bomba-relógio quando o projeto cresce. Um componente deve fazer apenas uma coisa, e fazer bem.
Por exemplo, se você tem um UserProfile, ele pode ser responsável por exibir os dados do usuário. Só isso. Já a lógica para buscar esses dados deve estar em outro lugar, como um hook customizado ou um serviço.
Pergunte-se sempre: “Esse componente faz mais do que deveria?” Se a resposta for sim, é hora de quebrá-lo em partes menores.
Esse é inclusive um dos princípios SOLID, chamando de Princípio da Responsabilidade Única. Esse, e todos os outros princípios, são abordados nesse curso incrível da Patrícia.,
2. Encapsule lógicas repetitivas em hooks customizados
Quantas vezes você já precisou de um loading enquanto espera por uma requisição? Ou gerenciar um estado de erro? Em vez de repetir essa lógica em vários componentes, crie hooks customizados que encapsulem esses comportamentos.
Por exemplo, um hook como useFetch pode cuidar de toda a lógica de requisição, estados de loading e erro. Agora, qualquer componente que precise buscar dados só precisa chamar o hook e consumir as informações.
3. Prefira compor componentes menores
Ao invés de criar um mega componente que faz tudo, pense em como dividir as responsabilidades em componentes menores que possam ser compostos. Essa abordagem facilita tanto a leitura quanto a reutilização do código.
Por que isso importa?
Separar responsabilidades e compor componentes menores não é só sobre organização. É sobre deixar o código preparado para mudanças inevitáveis. Tecnologias mudam, requisitos evoluem, e o código precisa acompanhar.
Além disso, componentes menores são mais fáceis de testar. Você pode testar o UserForm de forma isolada, sem precisar simular toda a lógica de dados do UserProfile. Isso economiza tempo e reduz o estresse quando algo quebra.
Conclusão: um papo sobre simplicidade
No fim das contas, organizar seus componentes é mais sobre simplicidade do que qualquer outra coisa.
Não é sobre seguir uma regra à risca, mas sobre pensar no futuro do código – no impacto que ele vai ter pra você e pra quem trabalhar nele depois.
Lembra que componentes escaláveis não são só sobre desempenho ou otimização. É sobre clareza, manutenção e aquela sensação de alívio quando você olha para um código e entende o que está acontecendo sem precisar de um café extra.
Se você quiser mergulhar ainda mais fundo, se liga só nesses conteúdos que separei pra você:
- Arquitetura no ecossistema de Front-End – Hipsters Ponto Tech #441
- Formação React: da teoria à prática em arquitetura e design de aplicações
- React Hooks: o que é e como funcionam?
- useState: aprenda o React Hook
- useMemo: aplicação React JS com mais performance
- Hook useCallback x useMemo: performance no React
- useRef: como funciona esse React Hook
Vida longa e próspera!