React is deliberately unopinionated — it's a library, not a framework. That flexibility is real, and so is the cost: every team ends up building their own implicit framework through accumulated conventions. The difference between a React codebase that's a pleasure to work in and one that's a maze of workarounds comes down to a handful of decisions made (or not made) early on.React é deliberadamente não opinativo — é uma biblioteca, não um framework. Essa flexibilidade é real, e o custo também: cada time acaba construindo seu próprio framework implícito por meio de convenções acumuladas. A diferença entre um codebase React que é um prazer de trabalhar e um labirinto de workarounds se resume a um punhado de decisões tomadas (ou não) no início.
1. Component Design: Small, Focused, and Composable1. Design de Componentes: Pequenos, Focados e Combináveis
The most valuable mental model in React is thinking in composition rather than inheritance or hierarchy. A component should do one thing and do it well. The signal that a component needs to be split: you need to scroll to understand what it renders.O modelo mental mais valioso no React é pensar em composição em vez de herança ou hierarquia. Um componente deve fazer uma coisa e fazê-la bem. O sinal de que um componente precisa ser dividido: você precisa rolar para entender o que ele renderiza.
// ✗ Monolithic component — renders, fetches, and manages all state in one place// ✗ Componente monolítico — renderiza, busca e gerencia todo o estado em um lugar
function UserDashboard() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const [selected, setSelected] = useState(null);
// 150 lines of JSX...// 150 linhas de JSX...
}
// ✓ Composed from focused pieces — each testable independently// ✓ Composto de partes focadas — cada uma testável independentemente
function UserDashboard() {
return (
<DashboardLayout>
<UserListContainer /> {/* fetches + manages list state *//* busca + gerencia estado da lista */}
<UserDetailPanel /> {/* reads selected user from store *//* lê usuário selecionado do store */}
</DashboardLayout>
);
}The children prop and render props are underused composition tools. Passing UI as props rather than hardcoding it inside a component decouples layout from behavior and makes reuse trivial without adding abstraction layers.A prop children e render props são ferramentas de composição subutilizadas. Passar UI como props em vez de codificá-la dentro de um componente desacopla layout de comportamento e torna a reutilização trivial sem adicionar camadas de abstração.
// Compound pattern — state stays in the parent, rendering is delegated// Padrão compound — estado fica no pai, renderização é delegada
<DataTable data={orders}>
<DataTable.Header />
<DataTable.Body renderRow={order => <OrderRow key={order.id} order={order} />} />
<DataTable.Pagination />
</DataTable>2. Hooks: The Five Mistakes Everyone Makes2. Hooks: Os Cinco Erros que Todo Mundo Comete
useEffect for everythinguseEffect para tudo
useEffect is the most misused hook. The React team's own guidance: most things you put in useEffect don't belong there. Data fetching belongs in a data-fetching library (React Query, SWR, RTK Query). Derived state belongs in a calculation during render, not a useEffect that syncs two state variables. Event responses belong in event handlers, not effects.useEffect é o hook mais mal usado. A própria orientação da equipe React: a maioria das coisas que você coloca no useEffect não pertence lá. Busca de dados pertence a uma biblioteca de busca de dados (React Query, SWR, RTK Query). Estado derivado pertence a um cálculo durante a renderização, não a um useEffect que sincroniza duas variáveis de estado. Respostas a eventos pertencem a event handlers, não a effects.
// ✗ useEffect to compute derived state// ✗ useEffect para calcular estado derivado
const [items, setItems] = useState([]);
const [total, setTotal] = useState(0);
useEffect(() => { setTotal(items.reduce((s, i) => s + i.price, 0)); }, [items]);
// ✓ Derive during render — no effect, no extra re-render// ✓ Derive durante a renderização — sem effect, sem re-renderização extra
const [items, setItems] = useState([]);
const total = items.reduce((s, i) => s + i.price, 0); // recalculated on render// recalculado na renderização
// ✗ useEffect to fetch on mount — no caching, no deduplication, no loading state management// ✗ useEffect para buscar no mount — sem cache, sem deduplicação, sem gerenciamento de loading
useEffect(() => { fetch('/api/users').then(r => r.json()).then(setUsers); }, []);
// ✓ React Query handles all of this — caching, background refetch, error boundaries// ✓ React Query lida com tudo isso — cache, refetch em background, error boundaries
const { data: users, isLoading } = useQuery({ queryKey: ['users'], queryFn: fetchUsers });Stale closures in effects and callbacksClosures obsoletas em effects e callbacks
// ✗ Stale closure — count is captured at 0, never updates// ✗ Closure obsoleta — count é capturado em 0, nunca atualiza
useEffect(() => {
const id = setInterval(() => console.log(count), 1000);
return () => clearInterval(id);
}, []); // missing count dependency// dependência count ausente
// ✓ Add the dependency, or use a ref for values you want to read without re-subscribing// ✓ Adicione a dependência ou use uma ref para valores que quer ler sem re-assinar
const countRef = useRef(count);
useEffect(() => { countRef.current = count; });
useEffect(() => {
const id = setInterval(() => console.log(countRef.current), 1000);
return () => clearInterval(id);
}, []);Missing cleanup in effectsCleanup ausente em effects
// ✗ Sets state after component unmounts — React 18 warning, potential memory leak// ✗ Define estado após o componente ser desmontado — aviso no React 18, potencial memory leak
useEffect(() => {
fetchUser(id).then(user => setUser(user));
}, [id]);
// ✓ AbortController cancels the fetch when component unmounts or id changes// ✓ AbortController cancela o fetch quando o componente é desmontado ou id muda
useEffect(() => {
const controller = new AbortController();
fetchUser(id, { signal: controller.signal }).then(user => setUser(user));
return () => controller.abort();
}, [id]);Overusing useState instead of useReducerUsar useState em excesso em vez de useReducer
When you have three or more state variables that change together, or transitions that involve complex conditions, useReducer is cleaner than multiple useState calls. It centralizes logic, makes state transitions explicit, and is trivial to test (pure function — no hooks required).Quando você tem três ou mais variáveis de estado que mudam juntas, ou transições que envolvem condições complexas, useReducer é mais limpo do que múltiplas chamadas de useState. Ele centraliza a lógica, torna as transições de estado explícitas e é trivial de testar (função pura — sem necessidade de hooks).
Not extracting custom hooksNão extrair custom hooks
Any stateful logic that appears in more than one component belongs in a custom hook. Custom hooks are the primary reuse mechanism in React — they extract the why (the logic) from the what (the rendering). A component that is 60% hook calls and 40% JSX should have its hook calls extracted.Qualquer lógica com estado que aparece em mais de um componente pertence a um custom hook. Custom hooks são o mecanismo primário de reutilização no React — eles extraem o porquê (a lógica) do o quê (a renderização). Um componente que é 60% chamadas de hook e 40% JSX deve ter suas chamadas de hook extraídas.
// Custom hook encapsulates the logic — component focuses on rendering// Custom hook encapsula a lógica — componente foca na renderização
function useUserSearch(initialQuery = '') {
const [query, setQuery] = useState(initialQuery);
const debouncedQuery = useDebounce(query, 300);
const { data, isLoading } = useQuery({
queryKey: ['users', 'search', debouncedQuery],
queryFn: () => searchUsers(debouncedQuery),
enabled: debouncedQuery.length > 1
});
return { query, setQuery, results: data ?? [], isLoading };
}
function UserSearchPage() {
const { query, setQuery, results, isLoading } = useUserSearch();
return (
<>
<SearchInput value={query} onChange={setQuery} />
<UserList users={results} loading={isLoading} />
</>
);
}3. State Management: The Decision Tree3. Gerenciamento de Estado: A Árvore de Decisão
useState or useReducer. Don't pull it into global state prematurely — it creates invisible coupling and makes the component harder to reuse.Use useState ou useReducer. Não coloque prematuramente em estado global — isso cria acoplamento invisível e torna o componente mais difícil de reutilizar.Context for low-frequency updates (theme, locale, auth user). Context re-renders all consumers on every change — avoid it for high-frequency state like form input values or scroll position.Use Context para atualizações de baixa frequência (tema, locale, usuário autenticado). Context re-renderiza todos os consumidores em cada mudança — evite para estado de alta frequência como valores de inputs de formulário ou posição de scroll.4. Performance: When memo Actually Helps (and When It Hurts)4. Performance: Quando memo Realmente Ajuda (e Quando Prejudica)
React.memo, useMemo, and useCallback are optimization tools, not default patterns. They add cost (memory for the cached value, CPU for the comparison). They're only beneficial when the comparison is cheaper than the work being avoided — which is not always true.React.memo, useMemo e useCallback são ferramentas de otimização, não padrões padrão. Eles adicionam custo (memória para o valor em cache, CPU para a comparação). Eles só são benéficos quando a comparação é mais barata do que o trabalho sendo evitado — o que nem sempre é verdade.
// ✗ Memoizing a cheap computation — the memo overhead exceeds the benefit// ✗ Memoizando um cálculo barato — o overhead do memo supera o benefício
const label = useMemo(() => `${firstName} ${lastName}`, [firstName, lastName]);
// ✓ Just compute it — string concatenation is not worth memoizing// ✓ Apenas calcule — concatenação de string não vale memoizar
const label = `${firstName} ${lastName}`;
// ✓ useMemo for genuinely expensive computations// ✓ useMemo para cálculos genuinamente custosos
const sortedAndFiltered = useMemo(() =>
items.filter(predicate).sort(comparator),
[items, predicate, comparator]
);
// ✓ useCallback when a function is passed to a memoized child// ✓ useCallback quando uma função é passada para um filho memoizado
const handleSelect = useCallback((id: string) => {
setSelected(id);
}, []); // only useful if UserRow is wrapped in React.memo// só útil se UserRow estiver envolvido em React.memoThe memo contract: React.memo on a child component only prevents re-renders if all its props are stable across parent renders. If the parent creates a new function or object inline, the prop is a new reference every render and memo does nothing. useCallback and useMemo are only useful when their result is passed to a React.memo-wrapped component or is itself used as a dependency.O contrato do memo: React.memo em um componente filho só previne re-renderizações se todas as suas props forem estáveis entre renderizações do pai. Se o pai cria uma nova função ou objeto inline, a prop é uma nova referência a cada renderização e memo não faz nada. useCallback e useMemo só são úteis quando seu resultado é passado para um componente envolvido em React.memo ou é usado como uma dependência.
5. Code Splitting and Lazy Loading5. Code Splitting e Lazy Loading
// Lazy load route-level components — initial bundle contains only the shell// Lazy load em componentes de nível de rota — bundle inicial contém apenas o shell
const UserPage = lazy(() => import('./features/users/UserPage'));
const OrdersPage = lazy(() => import('./features/orders/OrdersPage'));
function App() {
return (
<Suspense fallback={<PageSkeleton />}>
<Routes>
<Route path="/users" element={<UserPage />} />
<Route path="/orders" element={<OrdersPage />} />
</Routes>
</Suspense>
);
}
// Lazy load heavy non-route components too — charts, rich text editors, modals// Lazy load em componentes pesados não rota também — gráficos, editores, modais
const Chart = lazy(() => import('./components/Chart'));
{showChart && <Suspense fallback={<ChartSkeleton />}><Chart /></Suspense>}Route-level splitting should be non-negotiable. A React SPA that doesn't code-split loads the JavaScript for every feature on every page load — users pay the cost of features they never visit. Bundle analysis with vite-bundle-analyzer or webpack-bundle-analyzer makes the cost visible.O splitting por rota deve ser inegociável. Uma SPA React que não faz code-splitting carrega o JavaScript de cada feature em cada carregamento de página — os usuários pagam o custo de features que nunca visitam. A análise de bundle com vite-bundle-analyzer ou webpack-bundle-analyzer torna o custo visível.
6. Project Structure That Scales6. Estrutura de Projeto que Escala
src/
├── features/ # one folder per domain feature# uma pasta por feature de domínio
│ ├── users/
│ │ ├── components/ # presentational, feature-scoped# apresentacional, escopo de feature
│ │ ├── hooks/ # custom hooks for this feature# custom hooks para esta feature
│ │ ├── api/ # React Query hooks (useUsers, useCreateUser)# hooks React Query (useUsers, useCreateUser)
│ │ ├── store/ # Zustand slice or Redux slice if needed# slice Zustand ou Redux se necessário
│ │ ├── types.ts # domain types scoped to this feature# tipos de domínio com escopo desta feature
│ │ └── index.ts # public API — only export what other features need# API pública — exporte apenas o que outras features precisam
│ └── orders/
├── shared/ # truly shared UI — Button, Modal, Input, Table# UI verdadeiramente compartilhada — Button, Modal, Input, Table
│ └── components/
├── lib/ # framework setup — queryClient, store, router# setup de framework — queryClient, store, router
└── app/ # routes, App component, providers# rotas, componente App, providersThe index.ts barrel file at each feature boundary acts as an explicit public API. Other features import from features/users, not from features/users/components/UserCard/UserCard.tsx. This encapsulation makes refactoring within a feature safe and makes cross-feature dependencies explicit and reviewable.O arquivo barrel index.ts em cada limite de feature atua como uma API pública explícita. Outras features importam de features/users, não de features/users/components/UserCard/UserCard.tsx. Esse encapsulamento torna a refatoração dentro de uma feature segura e torna as dependências entre features explícitas e revisáveis.
7. Forms: Controlled vs. Uncontrolled7. Formulários: Controlado vs. Não Controlado
Controlled inputs (value={state}) are the React default and give you fine-grained control. But they re-render on every keystroke. For large forms (20+ fields), this is measurable. Uncontrolled inputs with useRef or a form library like React Hook Form (which uses uncontrolled inputs internally) are significantly faster for complex forms.Inputs controlados (value={state}) são o padrão React e dão controle granular. Mas re-renderizam a cada tecla digitada. Para formulários grandes (20+ campos), isso é mensurável. Inputs não controlados com useRef ou uma biblioteca de formulários como React Hook Form (que usa inputs não controlados internamente) são significativamente mais rápidos para formulários complexos.
// React Hook Form — uncontrolled under the hood, minimal re-renders// React Hook Form — não controlado por baixo, re-renderizações mínimas
const { register, handleSubmit, formState: { errors } } = useForm<UserForm>({
resolver: zodResolver(userSchema) // Zod schema for validation// schema Zod para validação
});
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('email')} />
{errors.email && <span>{errors.email.message}</span>}
</form>
);8. Error Boundaries: Failing Gracefully8. Error Boundaries: Falhando Graciosamente
A runtime error in any component without an Error Boundary crashes the entire React tree. Error Boundaries are class components (or use the react-error-boundary library) that catch render errors and display fallback UI instead of a blank page.Um erro em runtime em qualquer componente sem um Error Boundary derruba toda a árvore React. Error Boundaries são class components (ou use a biblioteca react-error-boundary) que capturam erros de renderização e exibem UI de fallback em vez de uma página em branco.
// Wrap every major section — not just the root// Envolva cada seção principal — não apenas a raiz
import { ErrorBoundary } from 'react-error-boundary';
function App() {
return (
<ErrorBoundary fallback={<AppCrash />}>
<Header />
<ErrorBoundary fallback={<SectionError section="dashboard" />}>
<Dashboard />
</ErrorBoundary>
<ErrorBoundary fallback={<SectionError section="sidebar" />}>
<Sidebar />
</ErrorBoundary>
</ErrorBoundary>
);
}9. Testing with React Testing Library9. Testes com React Testing Library
React Testing Library's guiding principle: test behavior, not implementation. Query by what the user sees (text, role, label), not by class names or component internals. Tests that break when you refactor without changing behavior are not tests — they're maintenance liabilities.O princípio orientador do React Testing Library: teste comportamento, não implementação. Consulte pelo que o usuário vê (texto, role, label), não por nomes de classes ou internos do componente. Testes que quebram quando você refatora sem mudar o comportamento não são testes — são passivos de manutenção.
// ✗ Tests implementation details — breaks on any refactor// ✗ Testa detalhes de implementação — quebra em qualquer refatoração
expect(wrapper.find('.user-card__title').text()).toBe('Alice');
// ✓ Tests what the user sees — survives any refactor that keeps the behavior// ✓ Testa o que o usuário vê — sobrevive a qualquer refatoração que mantém o comportamento
render(<UserCard user={alice} />);
expect(screen.getByRole('heading', { name: 'Alice' })).toBeInTheDocument();
// ✓ Testing an interaction// ✓ Testando uma interação
await userEvent.click(screen.getByRole('button', { name: /delete/i }));
expect(screen.queryByText('Alice')).not.toBeInTheDocument();Query priority in RTL: getByRole > getByLabelText > getByPlaceholderText > getByText. Roles map to what screen readers announce — tests that use them double as accessibility checks.Prioridade de queries no RTL: getByRole > getByLabelText > getByPlaceholderText > getByText. Roles mapeiam para o que leitores de tela anunciam — testes que os usam funcionam também como verificações de acessibilidade.
10. React 18 Concurrent Features10. Recursos Concorrentes do React 18
React 18's concurrent mode doesn't change how you write components — it changes how React decides when to render them. Two hooks expose this power directly to application code: useTransition and useDeferredValue. Both solve the same problem: keeping the UI responsive during expensive updates.O modo concorrente do React 18 não muda como você escreve componentes — muda como o React decide quando renderizá-los. Dois hooks expõem esse poder diretamente ao código da aplicação: useTransition e useDeferredValue. Ambos resolvem o mesmo problema: manter a UI responsiva durante atualizações custosas.
// useTransition — mark a state update as non-urgent
// UI stays responsive; the update is deferred until React has idle time// useTransition — marca uma atualização de estado como não urgente
// UI permanece responsiva; a atualização é adiada até o React ter tempo livre
const [isPending, startTransition] = useTransition();
function handleSearch(value: string) {
setInputValue(value); // urgent: update input immediately// urgente: atualiza o input imediatamente
startTransition(() => setSearchQuery(value)); // non-urgent: filter 10,000 items// não urgente: filtra 10.000 itens
}
return (
<>
<input value={inputValue} onChange={e => handleSearch(e.target.value)} />
{isPending && <Spinner />}
<ItemList query={searchQuery} /> {/* renders with deferred value *//* renderiza com valor adiado */}
</>
);
// useDeferredValue — defer the expensive part without owning the state update// useDeferredValue — adia a parte custosa sem possuir a atualização de estado
function SearchResults({ query }: { query: string }) {
const deferredQuery = useDeferredValue(query);
const isStale = query !== deferredQuery;
return (
<div style={{ opacity: isStale ? 0.6 : 1 }}> {/* visual stale indicator *//* indicador visual de stale */}
<ExpensiveList query={deferredQuery} />
</div>
);
}useTransition vs useDeferredValue: Use useTransition when you own the state update (you control when setState fires). Use useDeferredValue when you receive a value as a prop or from an external source you don't control. Both defer the expensive downstream render; they differ in where the deferral is applied.useTransition vs useDeferredValue: Use useTransition quando você possui a atualização de estado (você controla quando setState dispara). Use useDeferredValue quando você recebe um valor como prop ou de uma fonte externa que não controla. Ambos adiam a renderização downstream custosa; eles diferem em onde o adiamento é aplicado.
11. TypeScript Patterns That Make Components Safer11. Padrões TypeScript que Tornam Componentes Mais Seguros
React and TypeScript have strong synergy, but most teams only use TypeScript for basic prop typing. A few patterns go much further in making invalid states unrepresentable at compile time.React e TypeScript têm forte sinergia, mas a maioria dos times usa TypeScript apenas para tipagem básica de props. Alguns padrões vão muito além em tornar estados inválidos não representáveis em tempo de compilação.
// Discriminated unions for component variants — impossible to pass wrong combination// Unions discriminadas para variantes de componente — impossível passar combinação errada
type ButtonProps =
| { variant: 'primary'; onClick: () => void; href?: never }
| { variant: 'link'; href: string; onClick?: never };
// ✗ Type error — link variant requires href, not onClick// ✗ Erro de tipo — variante link requer href, não onClick
<Button variant="link" onClick={handleClick} />
// Polymorphic component — preserves the correct HTML element's prop types// Componente polimórfico — preserva os tipos de props do elemento HTML correto
type AsProps<T extends ElementType> = { as?: T } & ComponentPropsWithoutRef<T>;
function Text<T extends ElementType = 'p'>({ as, ...props }: AsProps<T>) {
const Tag = as ?? 'p';
return <Tag {...props} />;
}
<Text as="h1" id="title">Heading</Text> // id is valid; href would be a type error// id é válido; href seria erro de tipo
// Generic data components — no any, full type inference through// Componentes de dados genéricos — sem any, inferência de tipo completa
function DataTable<T>({ data, columns }: {
data: T[];
columns: { key: keyof T; label: string }[];
}) { ... }
<DataTable
data={users} // T inferred as User// T inferido como User
columns={[{ key: 'email', label: 'Email' }]} // key must be keyof User// key deve ser keyof User
/>12. Server Components and the Next.js App Router12. Server Components e o App Router do Next.js
React Server Components (RSC) — the foundation of Next.js 13+ App Router — fundamentally change the rendering model. Server Components run only on the server: they can access databases and secrets directly, never ship JavaScript to the client, and are ideal for data fetching. Client Components (marked 'use client') run in the browser and handle interactivity.React Server Components (RSC) — a base do App Router do Next.js 13+ — mudam fundamentalmente o modelo de renderização. Server Components rodam apenas no servidor: podem acessar bancos de dados e secrets diretamente, nunca enviam JavaScript para o cliente e são ideais para busca de dados. Client Components (marcados com 'use client') rodam no navegador e lidam com interatividade.
// Server Component — runs on server, zero JS sent to client, direct DB access// Server Component — roda no servidor, zero JS enviado ao cliente, acesso direto ao DB
// app/users/page.tsx (no 'use client' directive = Server Component by default)// app/users/page.tsx (sem diretiva 'use client' = Server Component por padrão)
export default async function UsersPage() {
const users = await db.users.findMany(); // direct DB call, no API needed// chamada direta ao DB, sem API necessária
return <UserList users={users} />;
}
// Client Component — needed for: useState, useEffect, event handlers, browser APIs// Client Component — necessário para: useState, useEffect, event handlers, browser APIs
'use client';
export function UserList({ users }: { users: User[] }) {
const [selected, setSelected] = useState<string | null>(null);
return (
<ul>
{users.map(user => (
<li key={user.id} onClick={() => setSelected(user.id)}>{user.name}</li>
))}
</ul>
);
}The decision rule: Default to Server Components. Add 'use client' only when you need: useState, useEffect, event listeners, browser-only APIs (window, localStorage), or third-party libraries that aren't RSC-compatible. Push 'use client' as far down the component tree as possible — a leaf interactive button doesn't require its entire parent tree to be a Client Component.A regra de decisão: Padrão para Server Components. Adicione 'use client' apenas quando precisar de: useState, useEffect, event listeners, APIs apenas do browser (window, localStorage), ou bibliotecas de terceiros não compatíveis com RSC. Empurre 'use client' o mais para baixo possível na árvore de componentes — um botão interativo de folha não requer que toda a árvore pai seja um Client Component.
13. Common Mistakes That Compound Over Time13. Erros Comuns que se Agravam com o Tempo
- Prop drilling beyond two levels. Passing props through three or more component layers creates invisible coupling. Extract to Context or move the consuming component closer to the state.Prop drilling além de dois níveis. Passar props por três ou mais camadas de componentes cria acoplamento invisível. Extraia para Context ou mova o componente consumidor para mais perto do estado.
- Huge component files with multiple exported components. One component per file (with its types and helpers in the same file) is the standard that makes navigation predictable. A 400-line file with five components is a refactoring trigger.Arquivos de componente enormes com múltiplos componentes exportados. Um componente por arquivo (com seus tipos e helpers no mesmo arquivo) é o padrão que torna a navegação previsível. Um arquivo de 400 linhas com cinco componentes é um gatilho de refatoração.
- Inconsistent async error handling. Components that don't show errors look like they're working. Always handle the error state in data-fetching components — an empty error state is worse than a visible one because it silently tells the user nothing went wrong when something did.Tratamento inconsistente de erros assíncronos. Componentes que não mostram erros parecem estar funcionando. Sempre trate o estado de erro em componentes de busca de dados — um estado de erro vazio é pior do que um visível porque silenciosamente diz ao usuário que nada deu errado quando algo deu.
- Re-creating context values inline.
<ThemeContext.Provider value={{ theme, toggleTheme }}>creates a new object on every render, causing all consumers to re-render. Memoize the context value.Recriar valores de context inline.<ThemeContext.Provider value={{ theme, toggleTheme }}>cria um novo objeto a cada renderização, fazendo todos os consumidores re-renderizarem. Memoize o valor do context.
Key TakeawaysPontos Principais
- Composition over abstraction. Split components by concern, not by line count. A component that renders three visual sections should be three components, not one with if-blocks.Composição em vez de abstração. Divida componentes por responsabilidade, não por contagem de linhas. Um componente que renderiza três seções visuais deve ser três componentes, não um com if-blocks.
- Stop putting useEffect where it doesn't belong. Derived state is a calculation. Event responses are handlers. Data fetching is React Query. If you're reaching for useEffect, ask whether one of these fits first.Pare de colocar useEffect onde não pertence. Estado derivado é um cálculo. Respostas a eventos são handlers. Busca de dados é React Query. Se você está recorrendo ao useEffect, pergunte se um desses se encaixa primeiro.
- Server state and client state are different problems. React Query for remote data, Context or Zustand for genuine client state. Most apps have far less client state than they think.Estado do servidor e estado do cliente são problemas diferentes. React Query para dados remotos, Context ou Zustand para estado genuíno do cliente. A maioria dos apps tem muito menos estado do cliente do que pensa.
- memo is a last resort, not a default. Profile first, then memoize where the profiler shows a genuine cost. Premature memoization adds complexity without measurable benefit.memo é um último recurso, não um padrão. Faça profiling primeiro, depois memoize onde o profiler mostra um custo genuíno. Memoização prematura adiciona complexidade sem benefício mensurável.
- Feature-based structure with explicit public APIs (index.ts) keeps cross-feature dependencies visible and refactoring safe. Layer-based structure (
components/,hooks/,services/at the root) doesn't scale past 10 features.Estrutura por feature com APIs públicas explícitas (index.ts) mantém as dependências entre features visíveis e a refatoração segura. Estrutura por camada (components/,hooks/,services/na raiz) não escala além de 10 features. - Test behavior, not implementation. RTL queries by role and text survive refactoring. Snapshot tests and class-name queries do not.Teste comportamento, não implementação. Queries RTL por role e texto sobrevivem à refatoração. Snapshot tests e queries por nome de classe não sobrevivem.