Um fluxo de decisão passo a passo para estruturar componentes React. Percorra cada pergunta em ordem -- suas respostas determinam a arquitetura, os padrões e as ferramentas que você precisa antes de escrever qualquer código.
Comece no Passo 1 e trabalhe em cada decisão sequencialmente. Cada passo se baseia no anterior. Ao final, você terá um projeto claro para o seu componente -- o que ele renderiza, como gerencia o estado, de onde vêm os dados e como ele se comunica com o resto do seu aplicativo.
Determine a saída visual primeiro. Todo o resto segue do que o usuário vê.
Pergunta
Se Sim
Se Não
Ele renderiza conteúdo estático (texto, imagens, layout)?
Mantenha simples -- um Server Component sem hooks ou estado
Vá para o Passo 2
Ele renderiza uma lista de itens?
Você precisa de .map(), props key únicas e provavelmente dados de um componente pai ou API
Considere um componente de item único
Ele renderiza outros componentes (composição)?
Projete a API de children ou baseada em slots agora
É um componente folha -- foque nas props
Ele não renderiza nada às vezes (condicional)?
Planeje sua estratégia de renderização condicional (ternário, &&, retorno antecipado)
Ele sempre renderiza -- caminho mais simples
Decisão chave: Se o componente renderiza apenas conteúdo estático sem interatividade, ele deve ser um Server Component (o padrão no Next.js App Router). Pare aqui -- você não precisa de JavaScript do lado do cliente.
Relacionado:Componentes -- padrões de composição, props, children | JSX e TSX -- regras de expressão, fragments, compilação JSX | Renderização Condicional -- padrões ternário, &&, retorno antecipado | Server Components -- quando e por que usá-los
Se o usuário clica, digita, passa o mouse, arrasta ou interage de outra forma com ele -- este é um Client Component.
Tipo de Interação
O que Você Precisa
Exemplo
Cliques de botão / toggles
Handler onClick, possivelmente useState
Curtir/descurtir, expandir/recolher
Entrada de texto
Input controlado com useState + onChange
Barra de pesquisa, campo de formulário
Efeitos de hover / foco
Apenas CSS (preferível) ou onMouseEnter/onFocus
Tooltip, gatilho de dropdown
Arrastar e soltar
Biblioteca de terceiros (dnd-kit) ou API Drag
Lista ordenável, quadro kanban
Atalhos de teclado
useEffect + addEventListener ou hook customizado
Cmd+K pesquisa, Escape para fechar
Decisão chave: Adicione "use client" no topo do arquivo apenas quando o componente realmente precisar de APIs do navegador, hooks ou manipuladores de eventos. Mantenha a fronteira do cliente o mais baixo possível na árvore.
Determine quais dados o componente gerencia internamente versus o que vem de fora.
Cenário de Estado
Abordagem Recomendada
Sem estado -- exibição pura
Apenas props. Componentes sem estado são mais fáceis de testar e reutilizar.
Toggle/contador simples (1-2 valores)
useState -- uma chamada por valor independente
Campos relacionados que mudam juntos (formulário com 3+ campos)
useState único com um objeto, useReducer para transições complexas, ou um store Zustand se o mesmo estado precisar ser lido/escrito de componentes irmãos
Máquina de estado complexa (assistente multi-etapas, estado de arrastar)
useReducer com ações explícitas, uma biblioteca de máquina de estados, ou um store Zustand com métodos de ação quando as etapas abrangem vários componentes
Valores derivados (lista filtrada, total computado)
Calcule durante a renderização -- não armazene em estado separado
Decisão chave: Se você está usando mais de 3-4 chamadas useState em um componente, pare e considere useReducer ou extrair um hook customizado.
Relacionado:useState -- funções atualizadoras, inicialização preguiçosa, batching | useReducer -- estado baseado em ações para lógica complexa | Configuração do Zustand -- store leve para estado entre componentes | Tipando Estado -- tipando formas de estado complexas | Máquinas de Estado -- padrões de estado finito para UI
A estratégia de busca de dados depende se você está em um Server Component ou Client Component.
Fonte de Dados
Melhor Abordagem
Evitar
Banco de Dados / ORM
Busque diretamente em um Server Component assíncrono
Expor consultas de banco de dados em código cliente
Sua própria API
Server Action ou fetch em Server Component
useEffect + fetch em um Client Component (causa cascateamentos)
API de terceiros
Fetch em Server Component, ou SWR/TanStack Query para o lado do cliente
useEffect + fetch bruto sem cache
Parâmetros de URL / search params
useParams() / useSearchParams() em Client Component, ou prop params em Server Component
Análise manual de window.location
Componente pai
Props -- sempre o caminho mais simples
Estado global para comunicação pai-filho
Entrada do usuário
Estado controlado (useState + onChange)
Refs não controladas para valores que você precisa validar ou exibir
Decisão chave: Prefira Server Components para buscar dados. Busque apenas no cliente quando precisar de atualizações em tempo real, atualizações iniciadas pelo usuário ou scroll infinito.
Determine como os dados e eventos fluem entre este componente e o resto do aplicativo.
Padrão de Comunicação
Quando Usar
Exemplo
Props para baixo
Componente pai passa dados para o filho
<UserCard name={user.name} />
Callbacks para cima
Filho notifica o pai sobre um evento
<SearchInput onSearch={handleSearch} />
Context
Muitos descendentes precisam dos mesmos dados (tema, autenticação, locale)
useContext(ThemeContext)
Store global (Zustand)
Estado transversal compartilhado por componentes não relacionados
Contagem do carrinho no cabeçalho + página de checkout
Estado da URL
Estado que deve sobreviver à atualização ou ser compartilhável
Filtros, paginação, aba ativa
Server Actions
Submissões de formulário ou mutações que atingem o servidor
<form action={submitForm}>
Decisão chave: Comece com props e callbacks. Use Context quando estiver passando props por 3+ níveis. Use Zustand quando vários componentes não relacionados precisarem do mesmo estado.
Decisão chave: Sempre tipa as props como uma interface (não inline) quando o componente tiver mais de 2 props. Exporte a interface para que os consumidores possam estendê-la ou referenciá-la.
Declarações de função para componentes (hoisted, genéricos limpos). Siga as regras de lint da sua equipe.
Decisão chave: Seja consistente dentro de um arquivo. Se o componente principal for uma declaração de função, os helpers também devem ser. Misturar estilos sinaliza uma diferença intencional onde não há nenhuma.
Otimize apenas quando você medir um problema. Otimização prematura adiciona complexidade sem benefício.
Sintoma
Ferramenta
Quando Aplicar
Filho re-renderiza quando o estado do pai muda
React.memo()
Apenas se o filho for caro para renderizar e as props não mudaram
Cálculo caro em cada renderização
useMemo
Apenas se o cálculo levar >1ms e as entradas raramente mudarem
Callback causa re-renderização do filho
useCallback
Apenas ao passar callbacks para filhos memoizados
Renderização de lista grande
Virtualização (react-window, tanstack-virtual)
Listas com 100+ itens visíveis no DOM
Componente pesado na carga inicial
React.lazy() + Suspense, ou next/dynamic
Modais, gráficos, editores -- qualquer coisa não visível na primeira pintura
Mudança de layout de imagens
next/image com width/height ou fill
Toda imagem deve declarar dimensões
Decisão chave: Analise primeiro com o React DevTools Profiler ou a aba Performance do Chrome. Nunca adicione memo, useMemo ou useCallback sem evidências de um problema de desempenho.
Relacionado:Padrões de Desempenho -- memoização, virtualização, divisão de código | useMemo -- quando a memoização ajuda (e quando prejudica) | useCallback -- referências estáveis para props de filhos | Compilador React -- memoização automática no React 19 | Suspense -- carregamento preguiçoso e streaming
react-hook-form + esquema zod + componentes de formulário shadcn/ui
Assistente multi-etapas
react-hook-form + useReducer para estado de etapa + esquemas zod por etapa
Upload de arquivos
<input type="file"> + FormData + server action ou URL pré-assinada
Validação em tempo real (enquanto digita)
react-hook-form mode "onChange" + zod
Atualizações otimistas
Hook useOptimistic para feedback instantâneo enquanto o servidor processa
Decisão chave: Para formulários simples, evite bibliotecas inteiramente -- useActionState e useFormStatus integrados do React 19 lidam com estado pendente, erros e aprimoramento progressivo com zero dependências. Use react-hook-form + zod quando precisar de validação complexa ou muitos campos.
Escolha sua estratégia de teste com base na complexidade do componente e no risco.
Tipo de Componente
Abordagem de Teste
Exibição pura (sem estado)
Teste de snapshot ou pule -- baixo risco, alta rotatividade
Interativo (cliques, entradas)
Teste de integração com React Testing Library -- simule ações do usuário
Formulário com validação
Teste o caminho feliz e cada regra de validação -- use cenários Gherkin como especificações
Busca de dados
Simule a API, teste os estados de carregamento/erro/sucesso
Hook customizado
Teste com renderHook do React Testing Library
Fluxo complexo (multi-etapas)
Teste de ponta a ponta com Playwright ou Cypress
Decisão chave: Teste o comportamento, não a implementação. Se você estiver afirmando sobre o estado interno ou a estrutura específica do DOM, o teste falhará a cada refatoração. Afirme sobre o que o usuário vê e faz.
Acessibilidade não é opcional -- é um requisito básico. Planeje-a no componente desde o início.
Elemento
Requisitos Mínimos
Botões
Use <button>, não <div onClick>. Adicione aria-label se for apenas ícone.
Inputs
Associe com <label> via htmlFor. Adicione aria-describedby para mensagens de erro.
Modais / diálogos
Capture o foco, retorne o foco ao fechar. Use role="dialog" e aria-modal="true".
Imagens
Sempre alt text. Imagens decorativas recebem alt="".
Navegação
Use landmarks <nav>, <main>, <aside>. Garanta que a navegação por teclado funcione.
Conteúdo dinâmico
Use regiões aria-live para conteúdo que atualiza sem recarregar a página.
Contraste de cores
Mínimo 4.5:1 para texto normal, 3:1 para texto grande (WCAG AA).
Decisão chave: Use elementos HTML semânticos primeiro (<button>, <nav>, <dialog>). Use atributos ARIA apenas quando a semântica nativa não cobrir seu caso de uso.
Organize por funcionalidade, não por tipo. Co-localize arquivos relacionados.
Tipo de Arquivo
Localização
Exemplo
Componente de Página
app/[route]/page.tsx
app/dashboard/page.tsx
Layout
app/[route]/layout.tsx
app/dashboard/layout.tsx
Componente de UI Compartilhado
components/[name].tsx
components/user-card.tsx
Componente Específico da Funcionalidade
app/[feature]/components/[name].tsx
app/dashboard/components/stats-chart.tsx
Hook Customizado
hooks/[name].ts ou co-localizado com seu componente
hooks/use-toggle.ts
Tipos
Co-localize no mesmo arquivo, ou types/[domain].ts para tipos compartilhados
types/user.ts
Server Actions
app/[feature]/actions.ts
app/dashboard/actions.ts
Decisão chave: Se um componente é usado por apenas uma página, co-localize-o com essa página. Se for usado por 2+ páginas, promova-o para components/. Não crie pastas para um único arquivo.
Relacionado:Next.js App Router -- convenções de arquivos, páginas e segmentos de rota | Layouts do Next.js -- layouts aninhados, templates e grupos de rotas
Início
|
v
1. O que ele renderiza? -----> Apenas estático? --> Server Component (fim)
|
v
2. Precisa de interatividade? -> Não? --> Server Component com props (fim)
|
v (sim: adicione "use client")
3. Qual estado ele possui? --> Nenhum? --> Client Component sem estado
| Simples? --> useState
| Complexo? --> useReducer ou hook customizado
v
4. De onde vêm os dados? --> Servidor? --> Busca no Server Component pai, passa como props
| Cliente? --> SWR ou TanStack Query
v
5. Como ele se comunica? ---> Pai-filho? --> Props + callbacks
| Transversal? --> Context ou Zustand
v
6. Defina tipos TypeScript
v
7. Escolha o estilo de exportação
v
8. Otimize apenas se medido
v
9. É um formulário? ---------> Simples? --> useActionState
| Complexo? --> react-hook-form + zod
v
10. Planeje a estratégia de teste
v
11. Adicione acessibilidade
v
12. Coloque na estrutura de arquivos
v
Fim -- comece a codificar