Deep dive em React — biblioteca para construção de interfaces via componentes declarativos. Foco em React 19+ (2026), Hooks, state management, performance e patterns modernos. Para JavaScript base, ver JavaScript Fundamentals. Para TypeScript com React, ver TypeScript. Para testes de componentes, ver Testes em JavaScript. Para HTML/CSS, ver HTML e CSS.
O que é
React é uma biblioteca JavaScript (não framework) criada pelo Facebook (2013) para construir UIs através de componentes compostos. Em 2026, é a base da maioria dos sistemas web modernos, e React 19 trouxe mudanças significativas: React Compiler, Server Components, Server Actions, e mudanças em APIs legacy.
Em 2026:
React 19 é o mainstream, com React Compiler opcional
Next.js 16 com Turbopack por default é o meta-framework dominante
TanStack Router + React Router 7 convergem
Vite 8 é o bundler padrão para SPAs
Server Components mudam fundamentalmente o modelo mental
Em entrevistas, o que diferencia um senior em React:
Entender reconciliation — como React decide o que re-renderizar
Hooks profundamente — dependency arrays, closures stale, rules of hooks
State management — quando useState, quando Context, quando Zustand/Redux, quando server state
Ainda experimental em 2026, mas ganhando adoção rápida
Server Components e Server Actions
Server Components (RSC) — componentes que rodam apenas no servidor, retornam markup, não vão ao client JS bundle.
// app/page.tsx — Server Component por default em Next.js 13+async function BlogList() { const posts = await db.posts.findMany(); // direto do DB, sem API return ( <ul> {posts.map(p => <li key={p.id}>{p.title}</li>)} </ul> );}
Benefícios:
Acesso direto a dados server-side (DB, filesystem)
Zero JavaScript no client para esses componentes
SEO e performance iniciais excelentes
Limitações:
Sem useState, useEffect, event handlers — são Server, não Client
Não podem usar browser APIs
Client Components
Para interatividade, use 'use client' no topo do arquivo:
// RUIM — ao remover item no meio, React confunde estado{items.map((item, i) => <Input key={i} defaultValue={item} />)}// Se remover o primeiro:// Antes: key=0 "a", key=1 "b", key=2 "c"// Depois: key=0 "b", key=1 "c"// React reutiliza DOM mas com valor errado
Hooks essenciais
Regras universais:
Só chame hooks no top level — nunca em condições, loops, nested functions
Só chame hooks de componentes React ou hooks customizados
Nome deve começar com use para hooks customizados
ESLint plugin eslint-plugin-react-hooks verifica estas regras.
useState
const [count, setCount] = useState(0);const [user, setUser] = useState<User | null>(null);// Lazy initialization — função só roda 1xconst [state, setState] = useState(() => expensiveInit());// Updater function — use quando o novo state depende do anteriorsetCount(prev => prev + 1);setCount(c => c + 1); // equivalente// Múltiplos updates na mesma função — batched em React 18+setCount(c => c + 1);setCount(c => c + 1); // final: count + 2
useEffect
Executa side effects após o render.
// Sem dependency — roda após CADA renderuseEffect(() => { console.log('runs on every render');});// Array vazio — roda uma vez (mount)useEffect(() => { console.log('runs on mount'); return () => console.log('runs on unmount');}, []);// Com dependências — roda quando dependency mudauseEffect(() => { fetch(`/api/users/${id}`).then(...);}, [id]);// CleanupuseEffect(() => { const controller = new AbortController(); fetch('/api/data', { signal: controller.signal }) .then(r => r.json()) .then(setData) .catch(err => { if (err.name !== 'AbortError') throw err; }); return () => controller.abort();}, []);
Armadilhas clássicas:
// BUG — stale closurefunction Counter() { const [count, setCount] = useState(0); useEffect(() => { const id = setInterval(() => setCount(count + 1), 1000); return () => clearInterval(id); }, []); // count nunca atualiza — closure captura count=0 return <div>{count}</div>;}// FIX — updater functionuseEffect(() => { const id = setInterval(() => setCount(c => c + 1), 1000); return () => clearInterval(id);}, []); // não precisa de count no deps
useEffect em Strict Mode: React 18+ em strict mode roda effects 2x em dev para detectar effects com side effects. Se seu effect quebra quando roda 2x, tem bug (falta de cleanup).
Escopo do state → Onde colocar
──────────────────────────────────────────
1 componente → useState local
Alguns componentes → lift state up
Vários componentes → Context (se não muda muito)
Global → Zustand / Redux / Jotai
Server state → React Query / SWR / tRPC
URL state → React Router / searchParams
Form state → React Hook Form
Regra fundamental:server state não é client state. Dados do servidor são cache local, não “estado” da aplicação.
Lift state up
// Compartilha state entre siblings pelo pai comumfunction Parent() { const [filter, setFilter] = useState(''); return ( <> <SearchBar filter={filter} onChange={setFilter} /> <ResultsList filter={filter} /> </> );}
Quando usar: state com muita derivação, granular reactivity.
Server state — React Query
O maior ganho de produtividade em React dos últimos anos. React Query (agora @tanstack/react-query) gerencia cache, refetch, invalidation, background updates — tudo que você faria manualmente.
const handleClick = useCallback(() => { doSomething(id);}, [id]);// Importante quando passar como prop para componente memoized<MemoChild onClick={handleClick} />
Em React 19 com Compiler: tudo isso é automático.
Profiler
import { Profiler } from 'react';<Profiler id="App" onRender={(id, phase, actualDuration) => { console.log(`${id} [${phase}] took ${actualDuration}ms`);}}> <App /></Profiler>
React DevTools tem Profiler UI — grava interações, mostra qual componente causou re-render, quanto tempo levou.
Stale closures em useEffect — use updater functions ou adicione ao deps
Missing dependencies no useEffect — eslint plugin pega
useEffect com setInterval sem cleanup — vazamento
Key como index — problemas em listas que reordenam
Mutação direta de state — state.push(x). Use spread ou bibliotecas como Immer.
Context para state que muda muito — re-renders em massa
Re-criar funções/objetos em cada render sem memo — passa novas props para filhos memoized
Otimizar sem medir — memo/useMemo/useCallback em tudo deixa código complexo
useState para server state — use React Query
{condition && <Component />} com 0 — renderiza ‘0’
Async no useEffect diretamente — useEffect não aceita async function
Esquecer cleanup em useEffect — subscriptions, timers, fetch
Re-render loop — setState no render sem condição
Props drilling — profundo demais, use Context ou state management
Strict mode quebrando effects — effects devem ser idempotentes
Forms controlled gigantes — re-render em cada keystroke. Use React Hook Form.
useMemo sem dependência correta — valor stale
ForwardRef confuso — em React 19, ref é prop normal
defaultProps em function components — removido em React 19, use destructuring defaults
Importar diretamente de react sem necessidade — dead imports
Esquecer 'use client' em Server Components com hooks
Na prática (da minha experiência)
Stack do MedEspecialista frontend:
1. React 19 + Next.js 16 (App Router, Server Components default)
2. TypeScript estrito desde o dia 1
3. Tailwind CSS para estilos
4. React Hook Form + Zod para formulários
5. React Query para server state
6. Zustand para client state global (pequeno)
7. shadcn/ui como base de componentes
8. Vitest + Testing Library + MSW para testes
9. Playwright para E2E
10. Storybook para desenvolvimento de componentes
Patterns que padronizei:
1. Feature-based folders — cada feature é auto-contida
2. Hooks customizados para lógica de domínio — componentes ficam “burros”, hooks têm a lógica
3. Server Components first, Client opt-in — 'use client' só quando preciso de estado/efeito
4. Zod schemas compartilhados backend/frontend — validação idêntica nos dois lados
5. Error boundaries por feature — erro em uma não derruba tudo
6. Suspense + React Query — loading states declarativos
7. TanStack Virtual para listas grandes — tabelas de 10k+ pacientes
Incidente memorável — stale closure em useEffect:
Notification bell tinha polling a cada 30s. State dos notifications não atualizava — stale closure clássico. Fix:
// Antes (bug)useEffect(() => { const id = setInterval(() => { fetchNotifications().then(newOnes => setAll([...all, ...newOnes])); }, 30000); return () => clearInterval(id);}, []); // all nunca atualiza// FixuseEffect(() => { const id = setInterval(() => { fetchNotifications().then(newOnes => setAll(prev => [...prev, ...newOnes])); }, 30000); return () => clearInterval(id);}, []);
Melhor ainda: React Query com refetchInterval. Substituí todo polling manual por queries automaticamente.
Outro incidente — Context causando re-renders globais:
UserContext armazenava { user, setUser, preferences, setPreferences, ... }. Qualquer mudança em qualquer campo re-renderizava a app inteira (1000+ componentes). Solução: split em 3 contexts separados (UserContext, PreferencesContext, ThemeContext) + Zustand para state com updates frequentes.
A lição principal: React é simples conceitualmente, mas produtivo exige patterns — server state separado de client state, hooks custom para reutilização, memoization consciente (ou React Compiler), Suspense para loading, error boundaries para resiliência. Domine o modelo mental (componentes, reconciliation, hooks) e o resto é prática.
How to explain in English
“React is a library for building UIs through composable components. In 2026, I use React 19 with Next.js 16 and the App Router, which makes Server Components the default. The mental shift is significant — components that don’t need interactivity run on the server, have direct database access, and ship zero JavaScript to the client. I only add 'use client' when I actually need state, effects, or browser APIs.
For state management, my rule is: server state is not client state. I use React Query for anything that comes from an API — it handles caching, refetch, invalidation, and optimistic updates. For global client state, Zustand is my default because it’s minimal and type-safe. For URL state, the router. For form state, React Hook Form with Zod resolvers.
The hooks I use most are useState, useEffect, and custom hooks that encapsulate feature logic. I follow Rules of Hooks strictly with the ESLint plugin. With React 19 Compiler becoming stable, I’m dropping most manual memoization — the compiler handles it automatically as long as I follow Rules of React.
For performance, I measure before optimizing. React DevTools Profiler is my first tool. Common wins: list virtualization for large datasets, code splitting for routes, proper key usage in lists, and avoiding unnecessary Context re-renders by splitting contexts by concern.
For testing, I use Vitest with React Testing Library and MSW for component and integration tests, and Playwright for end-to-end. The philosophy is test what users see, not implementation details. I use getByRole as the primary query because it aligns with accessibility.
Common pitfalls I watch for: stale closures in useEffect, mutation of state objects, Context causing unnecessary re-renders, forgetting cleanup in effects, and using useState for server state when React Query is the right tool.”
Frases úteis em entrevista
“Server state is not client state — React Query for one, Zustand for the other.”
“I start with Server Components, add 'use client' only when necessary.”
“Stale closures are the classic bug in useEffect — fix with updater functions or correct deps.”
“React Compiler in React 19 makes most manual memoization obsolete.”
“I use getByRole as my primary query in Testing Library — accessibility-aligned.”
“Performance: measure first with Profiler, optimize second.”
“Feature-based folder structure, not by type.”
“Zod schemas shared between backend and frontend — single source of validation truth.”
“Error boundaries isolate failures — one feature crashing doesn’t kill the app.”
“Forms: React Hook Form uncontrolled + Zod, not useState for every field.”
Key vocabulary
componente → component
renderização → rendering
reconciliação → reconciliation
estado → state
propriedades → props
gancho → hook
efeito colateral → side effect
montagem / desmontagem → mount / unmount
memoização → memoization
lazy loading → lazy loading
suspensão → suspense
limite de erro → error boundary
componente de servidor → server component
componente de cliente → client component
ação de servidor → server action
fechamento obsoleto → stale closure
controlado / não controlado → controlled / uncontrolled