“`html
Arquitetura • Componentização • Estado • Padrões
Dominando a Arquitetura de React
Um guia técnico, direto ao ponto, para você construir aplicações React com componentes previsíveis,
dados bem organizados e fronteiras claras entre UI, lógica e integrações.
1Comece pelo desenho: separar “quem renderiza” de “quem decide”
Em React, muita bagunça começa quando o componente faz tudo ao mesmo tempo:
busca dados, transforma domínio, decide loading/error, controla layout e ainda empurra eventos para qualquer lugar.
A arquitetura que funciona bem na prática é simples: UI renderiza e lógica decide.
O que eu busco ao organizar um projeto:
- Componentes de UI recebem dados prontos e callbacks simples. Eles não “sabem” detalhes de integrações.
- Camadas de lógica determinam estados (ex.: loading, falha, sucesso) e organizam fluxo.
- Integrações (APIs, gateways) expõem contratos estáveis, sem vazar detalhes para a UI.
Regra: se o componente precisa de “conhecimento” para decidir o que renderizar (além de props),
ele provavelmente está fazendo lógica demais. Empurre essa decisão para uma camada acima.
2Arquitetura por camadas: UI / Domain / Data (com fronteiras)
Você não precisa de “framework de arquitetura”. O que precisa é de fronteiras explícitas.
Eu gosto de pensar em três blocos:
-
UI: componentes visuais, acessibilidade e apresentação. Exemplos:
Button,
Modal,UserCard. -
Domain: regras e modelos do problema. Exemplos: validação de input, transformações,
contratos de caso de uso (o “o que” precisa acontecer). -
Data: como o mundo externo é consultado/atualizado. Exemplos: cliente HTTP, mapeamento de DTO,
cache e mecanismos de persistência.
Em termos de dependência, mantenha o fluxo assim:
UI → Domain → Data. Assim você evita que detalhes externos contaminem o resto do sistema.
ao separar camadas, seu “estado” costuma ficar mais previsível:
a UI recebe dados e exibe; a camada de lógica decide quando e por que o estado muda.
3Contratos de props e eventos: reduza acoplamento com “interfaces” claras
Uma arquitetura sólida não começa com pasta; começa com contratos.
Quando as props viram um “saco de variáveis” ou quando callbacks passam informação demais,
o sistema fica frágil.
Checklist prático para props:
-
Prefira props que representam estado do domínio ao invés de “flags soltas”.
Ex.:status: "idle" | "loading" | "error" | "success"é melhor do que espalhar
isLoading,hasError,errorMessagesem um modelo único. - Evite que UI conheça estrutura de dados interna de Data. Ela deve receber dados já prontos para renderizar.
-
Use callbacks com assinatura pequena e sem efeitos colaterais invisíveis.
Ex.:onSubmit(payload)ondepayloadé o domínio necessário. -
Quando precisar de “regras de habilitação” (ex.: botões desabilitados),
decida no nível acima e passe um boolean derivado para UI.
Anti-padrão: componentes que recebem setState inteiro.
Isso acopla UI ao mecanismo de estado e dificulta testes e evolução.
4Fluxo de estado e renderizações: controle quem possui o quê
O ponto central em arquitetura é: quem é o dono do estado?
Em React, ter uma estratégia clara evita loops de re-render e inconsistências.
Minha regra de bolso:
- Estado local fica no componente que controla interações imediatas (form input, toggles, tabs).
-
Estado de dados (vindos de integrações) fica no nível de lógica/camada de orquestração,
e a UI consome o resultado. -
Estado compartilhado (entre rotas ou componentes distantes) deve ser promovido de forma intencional.
Se você não conseguir explicar o “porquê” em uma frase, talvez não devesse existir.
Para reduzir ruído:
não compute tudo em render. Transformações caras e mapeamentos podem ser tratados na camada acima
(ou com memoização local quando fizer sentido).
ao mover “decisão” para fora da UI, você consegue testar lógica com entradas/saídas e,
na UI, focar em render + acessibilidade + eventos.
5Exemplo: orquestrando dados e mantendo UI limpa
Abaixo, um exemplo com fronteiras: UI recebe dados e trata eventos; a camada de
orquestração decide estado e chama o “use case” (domain) que por sua vez usa a integração (data).
O resultado é previsível, modular e fácil de evoluir.
{`// UI: apresentação pura
type UserView = {
id: string;
name: string;
email: string;
};
type UserCardProps = {
status: "loading" | "ready" | "error";
user?: UserView;
errorMessage?: string;
onRetry: () => void;
};
function UserCard({ status, user, errorMessage, onRetry }: UserCardProps) {
if (status === "loading") return Carregando...;
if (status === "error") {
return (
Falha ao carregar usuário: {errorMessage}
);
}
return (
{user?.name}
{user?.email}
);
}
// Domain: caso de uso (regras + contrato)
type FetchUser = (userId: string) => Promise<UserView>
function createFetchUser({ userRepository }: { userRepository: UserRepository }): FetchUser {
return async (userId) => {
// regras de domínio/transformações podem ficar aqui
const dto = await userRepository.getById(userId);
return {
id: dto.id,
name: dto.fullName,
email: dto.emailAddress,
};
};
}
// Data: integração (ex.: HTTP + mapeamento de DTO)
type UserDto = {
id: string;
fullName: string;
emailAddress: string;
};
type UserRepository = {
getById: (userId: string) => Promise<UserDto>
};
function createUserRepository({ http }: { http: { get: (url: string) => Promise<any> } }): UserRepository {
return {
async getById(userId) {
const res = await http.get("/api/users/" + encodeURIComponent(userId));
return {
id: res.id,
fullName: res.name,
emailAddress: res.email,
};
},
};
}
// Orquestração: estado e fluxo (não é UI)
function UserCardContainer({ userId }: { userId: string }) {
const [status, setStatus] = React.useState<"loading" | "ready" | "error">("loading");
const [user, setUser] = React.useState<UserView | undefined>(undefined);
const [errorMessage, setErrorMessage] = React.useState<string | undefined>(undefined);
const fetchUser = React.useMemo(() => {
const http = /* instância do seu cliente HTTP */ (null as any);
const userRepository = createUserRepository({ http });
return createFetchUser({ userRepository });
}, []);
const load = React.useCallback(async () => {
setStatus("loading");
setErrorMessage(undefined);
try {
const data = await fetchUser(userId);
setUser(data);
setStatus("ready");
} catch (err: any) {
setUser(undefined);
setErrorMessage(err?.message ?? "Erro desconhecido");
setStatus("error");
}
}, [fetchUser, userId]);
React.useEffect(() => {
load();
}, [load]);
return (
);
}`}
Por que funciona: a UI só “renderiza” conforme status e dados.
O fluxo de erro/loading e o acesso aos dados ficam na camada de orquestração.
Quer deixar sua base ainda mais forte?
Leia também outros posts para evoluir seu React por camadas, melhorar padrões de componentes e organizar
melhor estado e integrações no dia a dia.
“`
Sou Apaixonado pela programação e estou trilhando o caminho de ter cada diz mais conhecimento e trazer toda minha experiência vinda do Design para a programação resultando em layouts incríveis e idéias inovadoras! Conecte-se Comigo!