Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA

Guia completo

React: o guia completo para aprender depois do JavaScript

O que é React, o que estudar em cada etapa, onde as pessoas travam e quando não usar React — o mapa da trilha por quem ensina isso todo dia.

20 lições em trilha26 artigos no guiaAtualizado em agosto de 2026

React é uma biblioteca de JavaScript para construir interface. Você descreve como a tela deve ficar para cada estado dos dados, e o React se encarrega de mexer no HTML. É um próximo passo comum para quem já consegue escrever pequenas interfaces em JavaScript sem apoio.

Este guia é o mapa da trilha: o que estudar, em que ordem, onde as pessoas travam e — a parte que quase ninguém escreve — quando não usar React. Todos os exemplos são de uma agenda de clínica veterinária: consultas, pets, tutores. O código foi executado no Node 24.16.0 com React 19.2.8, e a saída embaixo de cada bloco é a que apareceu no terminal. Os cinco exemplos que precisam de um navegador rodam dentro do jsdom — um DOM de mentira feito em Node —, e as linhas que montam esse DOM falso são as mesmas nos cinco, então ficaram fora dos blocos para não repetir.

Um painel que você redesenha no papel antes de tocar na parede

Quando a agenda da clínica muda, você não quer escrever uma sequência de ordens como “apague esta linha, mova aquela placa, pinte o contador”. Você quer entregar uma nova versão do painel para os dados atuais e deixar alguém aplicar apenas as diferenças. Esse é o problema central que React assume.

O nome técnico é renderização declarativa baseada em componentes e estado. Você descreve a árvore desejada; React compara versões e atualiza o DOM. No primeiro exemplo do guia, identifique qual dado mudou, qual componente voltou a executar e qual parte do HTML final ficou diferente. Essa sequência é o modelo mental que sustenta o restante da trilha.

O problema é manter a tela coerente com os dados. Parece banal até a segunda tela do projeto.

Veja a agenda da clínica escrita com DOM puro, sem biblioteca nenhuma. Existem duas coisas na página: um resumo com o total de consultas confirmadas e a lista das consultas. Um tutor liga e confirma o horário do Thor, e você atualiza o dado e o item da lista:

js
// a página tem só isto: <p id="resumo"></p><ul id="lista"></ul>
const consultas = [
  { id: 1, pet: 'Amora', status: 'confirmada' },
  { id: 2, pet: 'Thor', status: 'aguardando' },
];

function desenhar() {
  document.querySelector('#lista').innerHTML = consultas
    .map((c) => `<li>${c.pet} — ${c.status}</li>`)
    .join('');
  document.querySelector('#resumo').textContent =
    `${consultas.filter((c) => c.status === 'confirmada').length} confirmadas`;
}

desenhar();

// o tutor do Thor liga e confirma: atualizo o dado e o item da lista
consultas[1].status = 'confirmada';
document.querySelector('#lista').children[1].textContent = 'Thor — confirmada';

console.log('resumo:', document.querySelector('#resumo').textContent);
console.log('lista :', [...document.querySelectorAll('#lista li')].map((li) => li.textContent).join(' | '));
resumo: 1 confirmadas lista : Amora — confirmada | Thor — confirmada

A lista diz que as duas estão confirmadas. O resumo insiste em uma. Ninguém escreveu um bug: faltou lembrar de atualizar o segundo lugar que dependia do mesmo dado. Com três lugares, você esquece dois. Com uma tela inteira, você esquece sempre.

Agora o mesmo caso em React. Você não atualiza a tela: você troca o dado e descreve, uma vez só, como a tela sai de qualquer dado:

jsx
import { renderToStaticMarkup } from 'react-dom/server';

function Agenda({ consultas }) {
  const confirmadas = consultas.filter((c) => c.status === 'confirmada');
  return (
    <section>
      <p>{confirmadas.length} confirmadas</p>
      <ul>
        {consultas.map((c) => (
          <li key={c.id}>
            {c.pet}{c.status}
          </li>
        ))}
      </ul>
    </section>
  );
}

const antes = [
  { id: 1, pet: 'Amora', status: 'confirmada' },
  { id: 2, pet: 'Thor', status: 'aguardando' },
];
const depois = antes.map((c) => (c.id === 2 ? { ...c, status: 'confirmada' } : c));

console.log(renderToStaticMarkup(<Agenda consultas={antes} />));
console.log(renderToStaticMarkup(<Agenda consultas={depois} />));
<section><p>1 confirmadas</p><ul><li>Amora — confirmada</li><li>Thor — aguardando</li></ul></section> <section><p>2 confirmadas</p><ul><li>Amora — confirmada</li><li>Thor — confirmada</li></ul></section>

As duas linhas são o mesmo componente renderizado com renderToStaticMarkup, antes e depois de trocar o status do Thor. O resumo e a lista mudaram juntos porque os dois nascem do mesmo array. É isso, e só isso, que o React faz de essencial. O resto é ecossistema.

E o que ele não resolve: React não é framework completo. Ele não traz rota, não traz requisição HTTP, não traz formulário validado, não traz autenticação e não traz opinião sobre pasta. Cada uma dessas decisões continua sua. Quem entra esperando um Rails do front-end sai frustrado na primeira semana.

Quanto de JavaScript é pré-requisito de verdade

A resposta honesta é: menos do que dizem, e mais do que você quer. Não é a linguagem inteira — é um recorte pequeno que aparece em toda linha de React.

Este script não tem uma única importação de React, e ainda assim é literalmente o que um componente faz por dentro:

js
const consultas = [
  { id: 1, pet: 'Amora', status: 'confirmada', tutor: { nome: 'Ana', telefone: '11 99999-0001' } },
  { id: 2, pet: 'Thor', status: 'aguardando', tutor: { nome: 'Bruno' } },
  { id: 3, pet: 'Nina', status: 'confirmada', tutor: { nome: 'Carla', telefone: '11 99999-0003' } },
];

// desestruturação + template literal
const linha = ({ pet, tutor }) => `${pet} (${tutor.nome})`;

// filter + map: é isso que vira lista na tela
console.log(consultas.filter((c) => c.status === 'confirmada').map(linha));

// optional chaining + nullish: campo que pode não existir
console.log(consultas.map((c) => c.tutor?.telefone ?? 'sem telefone'));

// spread: trocar um item sem alterar o array original
const atualizado = consultas.map((c) => (c.id === 2 ? { ...c, status: 'confirmada' } : c));
console.log(atualizado[1].status, consultas[1].status);
console.log('mesma referência?', atualizado === consultas);

// curto-circuito com && : é o "if" do JSX
const alerta = consultas.length > 2 && 'agenda cheia';
console.log(alerta);
[ 'Amora (Ana)', 'Nina (Carla)' ] [ '11 99999-0001', 'sem telefone', '11 99999-0003' ] confirmada aguardando mesma referência? false agenda cheia

Repare na terceira linha da saída: atualizado[1] virou confirmada e consultas[1] continua aguardando. O map com spread produziu um array novo sem tocar no original. Guarde essa frase — ela reaparece na seção de erros, e é o motivo número um de tela que não atualiza.

Se essas cinco construções ainda são estranhas, pare aqui e volte para o guia de JavaScript. Aprender React antes disso não é mais rápido: você troca uma dificuldade que tem nome por uma que parece mágica.

O que não é pré-requisito: this, protótipos, classes, closures em profundidade, geradores, Proxy. Você vai usar closure o tempo todo sem precisar saber que se chama assim.

O mapa da trilha: das props ao estado compartilhado

A ordem abaixo é a da trilha de React, e ela não é a ordem da documentação oficial. É a ordem em que os assuntos param de doer.

etapa o que você aprende o que consegue fazer no fim
1. Componente e JSX função que devolve marcação, chaves para interpolar uma tela estática montada em pedaços
2. Props passar dado de cima para baixo, children um cartão de consulta reaproveitado com dados diferentes
3. Listas e condicional map com key, &&, ternário a agenda inteira, ainda sem interação
4. Eventos e useState onClick, onChange, estado que dispara re-render confirmar uma consulta e ver a tela mudar
5. Estado imutável e formulário controlado spread, value + onChange um formulário de novo agendamento que valida
6. useEffect e dados de fora efeito, dependências, limpeza, carregando e erro a agenda vinda de uma API de verdade
7. Composição e contexto children, useContext, hook customizado tema, usuário logado e lógica repetida em um lugar só
8. Rotas caminho, parâmetro, query string /consultas/42 funcionando com F5 e link compartilhável

Duas observações que economizam meses.

A primeira: as etapas 1 a 4 sustentam o restante do trabalho. Componente, JSX, props e estado reaparecem em toda funcionalidade; correr até hooks exóticos sem dominar essa base só troca entendimento por receita. Fique nas quatro primeiras até conseguir montar uma tela nova e explicar por que ela atualiza.

A segunda: só passe para a etapa 6 quando a 5 estiver automática. useEffect é onde a trilha cobra a fatura de tudo que ficou pela metade — e é onde nascem os loops infinitos.

Os cinco hooks que resolvem noventa por cento do trabalho

Hook é uma função que dá memória e ciclo de vida a um componente. Existem mais de dez na API; cinco pagam o aluguel.

Comece por useState. Ele guarda um valor entre renderizações e avisa o React quando esse valor muda. Este é o componente rodando de verdade no navegador — renderizado com react-dom/client dentro do jsdom, com um clique disparado no botão do Thor:

jsx
import { useState } from 'react';
import { createRoot } from 'react-dom/client';
import { flushSync } from 'react-dom';

const iniciais = [
  { id: 1, pet: 'Amora', status: 'confirmada' },
  { id: 2, pet: 'Thor', status: 'aguardando' },
  { id: 3, pet: 'Nina', status: 'aguardando' },
];

function Agenda() {
  const [consultas, setConsultas] = useState(iniciais);
  const confirmadas = consultas.filter((c) => c.status === 'confirmada').length;

  function confirmar(id) {
    setConsultas((atuais) =>
      atuais.map((c) => (c.id === id ? { ...c, status: 'confirmada' } : c))
    );
  }

  return (
    <section>
      <p id="resumo">{confirmadas} de {consultas.length} confirmadas</p>
      {consultas.map((c) => (
        <button key={c.id} id={`btn-${c.id}`} onClick={() => confirmar(c.id)}>
          {c.pet}{c.status}
        </button>
      ))}
    </section>
  );
}

// tela() só lê o que está na página, para eu imprimir o antes e o depois
function tela() {
  return [
    document.getElementById('resumo').textContent,
    ...[...document.querySelectorAll('button')].map((b) => b.textContent),
  ].join(' / ');
}

const root = createRoot(document.getElementById('root'));
flushSync(() => root.render(<Agenda />));
console.log('antes :', tela());

flushSync(() => document.getElementById('btn-2').click());
console.log('depois:', tela());
antes : 1 de 3 confirmadas / Amora — confirmada / Thor — aguardando / Nina — aguardando depois: 2 de 3 confirmadas / Amora — confirmada / Thor — confirmada / Nina — aguardando

Um clique, e o resumo e o botão mudaram na mesma renderização. É o exemplo do começo do guia, agora com interação real.

Os outros quatro, em ordem de quanto você vai usar:

  • useEffect — sincronizar o componente com algo de fora: buscar dados, assinar um evento, mexer no document.title. É o hook mais usado e o mais usado errado. Se o array de dependências existe para calar um aviso, o efeito está errado.
  • useRef — guardar um valor que sobrevive ao re-render sem disparar re-render, e pegar o nó do DOM (input.current.focus()).
  • useContext — ler um valor publicado lá em cima da árvore sem passar por todo mundo no caminho.
  • useReducer — quando o estado tem muitas transições e o setState virou uma sopa de if. Um agendamento com rascunho, validando, salvo e erro é o caso clássico.

useMemo e useCallback não estão nos cinco. Eles são otimização, e otimização sem medição é chute. Espalhá-los por hábito aumenta dependências e torna o código mais difícil de ler sem provar ganho. Use quando o perfil do DevTools apontar, não antes.

Onde mora cada dado: local, contexto, URL ou servidor

Esta é a decisão que mais separa código profissional de código de tutorial, e ela quase nunca é ensinada. Toda vez que um dado novo aparece na tela, você escolhe onde ele vive. Errar aqui não gera erro no console — gera um projeto que vai ficando difícil.

O dado cabe num link compartilhado? O dado vem do servidor? Componentes distantes leem o mesmo dado? Estado local, no componente mais próximo de quem usa vive na URL vive no cache do servidor vive no contexto sim sim sim não não não

Em forma de tabela, com o critério explícito e o custo de errar:

onde use quando ferramenta o preço de errar
Estado local só este componente e os filhos dele leem o dado useState, useReducer subir estado que ninguém mais usa faz a tela inteira re-renderizar por um campo de busca
URL o dado descreve o que está sendo visto: filtro, aba, página, id React Router, searchParams F5 apaga o filtro, o usuário não consegue mandar o link e o botão voltar mente
Cache de servidor o dado é uma cópia do banco, não a verdade fetch com estado de carregando e erro, TanStack Query você reinventa cache, revalidação e corrida de requisição, sempre pior
Contexto muitos componentes distantes leem, e o dado muda pouco Context API contexto que muda a cada tecla re-renderiza metade do app

A regra por trás da tabela: escolha pelo tempo de vida do dado, não pela distância entre os componentes. Prop drilling é chato, contexto é caro — e quase toda dor de prop drilling se resolve com composição (children) antes de merecer um contexto.

O caso da URL é o mais subestimado, e o mais fácil de provar. Aqui o filtro da agenda não está em estado nenhum: ele é lido da query string, e o mesmo endereço sempre produz a mesma tela.

jsx
import { renderToStaticMarkup } from 'react-dom/server';

const consultas = [
  { id: 1, pet: 'Amora', status: 'confirmada', vet: 'Dra. Lima' },
  { id: 2, pet: 'Thor', status: 'aguardando', vet: 'Dr. Paz' },
  { id: 3, pet: 'Nina', status: 'confirmada', vet: 'Dr. Paz' },
  { id: 4, pet: 'Bidu', status: 'cancelada', vet: 'Dra. Lima' },
];

function Painel({ busca }) {
  const params = new URLSearchParams(busca);
  const status = params.get('status') ?? 'todas';
  const vet = params.get('vet') ?? 'todos';

  const visiveis = consultas.filter(
    (c) => (status === 'todas' || c.status === status) && (vet === 'todos' || c.vet === vet)
  );

  return (
    <ul>
      {visiveis.map((c) => (
        <li key={c.id}>{`${c.pet} (${c.vet})`}</li>
      ))}
    </ul>
  );
}

for (const busca of ['', '?status=confirmada', '?status=confirmada&vet=Dr.%20Paz']) {
  console.log(busca || '(sem filtro)');
  console.log(renderToStaticMarkup(<Painel busca={busca} />));
}
(sem filtro) <ul><li>Amora (Dra. Lima)</li><li>Thor (Dr. Paz)</li><li>Nina (Dr. Paz)</li><li>Bidu (Dra. Lima)</li></ul> ?status=confirmada <ul><li>Amora (Dra. Lima)</li><li>Nina (Dr. Paz)</li></ul> ?status=confirmada&vet=Dr.%20Paz <ul><li>Nina (Dr. Paz)</li></ul>

Três URLs, três telas, zero useState. A recepcionista da clínica manda o link para a veterinária e a tela chega igual do outro lado. Com o filtro em estado local isso é impossível — e o botão voltar do navegador passa a ignorar a última coisa que a pessoa fez.

Vite, Next.js ou React puro: quando cada um

React sozinho é um pacote no npm. Alguém precisa transformar JSX em JavaScript, servir os arquivos e recarregar a tela enquanto você digita. Hoje existem três respostas razoáveis, e a escolha é mais simples do que a internet faz parecer.

Vite, para aprender e para aplicação atrás de login. É a escolha desta trilha porque mantém a montagem pequena e deixa os conceitos do React visíveis. Uma agenda de clínica que só abre depois do login normalmente não precisa de renderização no servidor para entregar a primeira versão.

Para dar um número em vez de adjetivo: este é o build de produção da agenda inteira — React 19.2.8, um componente com estado e evento, StrictMode ligado — com Vite 8.2.2 num MacBook. Rodei três vezes seguidas: o tamanho saiu igual nas três, e o tempo variou entre 48 e 51 ms. Abaixo, o terminal de uma delas:

bash
npx vite build
vite v8.2.2 building client environment for production... transforming... ✓ 14 modules transformed. rendering chunks... computing gzip size... dist/index.html 0.24 kB │ gzip: 0.19 kB dist/assets/index-CJTituTL.js 190.74 kB │ gzip: 60.12 kB

✓ built in 48ms

Guarde os 60 kB comprimidos: eles voltam mais adiante, quando a pergunta for quando não usar React.

Next.js, quando a página precisa ser encontrada no Google. Site institucional, blog, catálogo, loja. Ele resolve renderização no servidor, rotas por arquivo e metadados — e cobra em conceitos novos: cliente e servidor, cache, streaming. É excelente, e é a segunda coisa a aprender, não a primeira.

React puro num <script>, quase nunca. Existe, funciona para colar um componente numa página legada, e não é onde você deve começar.

Os erros que denunciam que você entendeu errado

Três bugs. Nenhum deles é de sintaxe, e cada um revela um modelo mental furado.

1. Mutar o estado: o dado muda e a tela não

Este é o campeão. A única diferença para o exemplo do useState lá em cima é que aqui o confirmar altera o objeto no lugar e devolve o mesmo array:

jsx
// tudo o mais é igual ao exemplo anterior; só o confirmar mudou
function confirmar(id) {
  setConsultas((atuais) => {
    atuais.find((c) => c.id === id).status = 'confirmada';
    return atuais;
  });
}

// e, depois do clique, imprimo também o array que foi para o useState
console.log('dado  :', iniciais.map((c) => `${c.pet}=${c.status}`).join(' / '));
antes : 1 de 3 confirmadas / Amora — confirmada / Thor — aguardando / Nina — aguardando depois: 1 de 3 confirmadas / Amora — confirmada / Thor — aguardando / Nina — aguardando dado : Amora=confirmada / Thor=confirmada / Nina=aguardando

Leia as três linhas com calma. A tela é idêntica antes e depois. O dado, na terceira linha, mudou: o Thor está confirmado no array. O React comparou a referência antiga com a nova, viu o mesmo array e concluiu, corretamente, que não havia nada para redesenhar.

Não existe mensagem de erro para isso — é o pior tipo de bug. A correção é sempre criar um valor novo, com map e spread, atualizando o estado sem mutar o valor anterior.

2. Hook dentro de if: o estado do vizinho

Hook não é chamado por nome; é chamado por posição. O React guarda os valores numa lista e conta a ordem das chamadas. Um if em volta quebra a contagem:

jsx
import { useState } from 'react';
import { createRoot } from 'react-dom/client';
import { flushSync } from 'react-dom';

function FiltroDaAgenda({ mostrarBusca }) {
  if (mostrarBusca) {
    const [termo] = useState('');
    return <input defaultValue={termo} />;
  }
  const [status] = useState('todas');
  return <span>{status}</span>;
}

// o mesmo componente, no mesmo lugar da árvore, com a prop trocada
const root = createRoot(document.getElementById('root'));

flushSync(() => root.render(<FiltroDaAgenda mostrarBusca={false} />));
console.log('render 1:', document.getElementById('root').innerHTML);

flushSync(() => root.render(<FiltroDaAgenda mostrarBusca={true} />));
console.log('render 2:', document.getElementById('root').innerHTML);
render 1: <span>todas</span> render 2: <input value="todas">

O useState('') devolveu 'todas'. O campo de busca nasceu preenchido com o estado do filtro anterior, porque os dois ocupam a primeira posição da lista de hooks do componente. Sem erro, sem aviso.

Acrescente um segundo useState dentro do if — uma paginação, por exemplo — e aí sim o React percebe a contagem trocada e reclama alto:

React has detected a change in the order of Hooks called by FiltroDaAgenda. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://react.dev/link/rules-of-hooks

Previous render Next render

  1. useState useState
  2. undefined useState ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Error: Rendered more hooks than during the previous render.

Por isso a regra: todo hook no topo do componente, sempre, sem condição e sem laço. O if vai depois, no que a função devolve.

3. Lista sem key

jsx
import { renderToStaticMarkup } from 'react-dom/server';

const consultas = [
  { id: 1, pet: 'Amora', status: 'confirmada' },
  { id: 2, pet: 'Thor', status: 'aguardando' },
];

function Lista() {
  return (
    <ul>
      {consultas.map((c) => (
        <li>{c.pet}</li>
      ))}
    </ul>
  );
}

renderToStaticMarkup(<Lista />); // basta renderizar uma vez para o aviso sair
Each child in a list should have a unique "key" prop.

Check the render method of Lista. See https://react.dev/link/warning-keys for more information.

A página funciona, o aviso some no meio do console, e o bug aparece três semanas depois: você remove a consulta do meio e o campo de texto de uma linha “pula” para outra. A key é como o React identifica cada item entre duas renderizações. Use o id do banco. Nunca use o índice do map numa lista que reordena, filtra ou remove.

O que mudou no React 19 e o que virou lenda

Muito material em português ainda descreve um React de 2021. Três correções.

ref agora é uma prop comum. forwardRef não é mais necessário em componente de função. Este código roda em React 19.2.8, com o foco indo para o input que está dentro de outro componente:

jsx
import { useRef, version } from 'react';
import { createRoot } from 'react-dom/client';
import { flushSync } from 'react-dom';

function CampoBusca({ ref, ...props }) {
  return <input ref={ref} placeholder="Buscar pet..." {...props} />;
}

function Painel() {
  const campo = useRef(null);
  return (
    <>
      <CampoBusca ref={campo} />
      <button id="focar" onClick={() => campo.current.focus()}>Buscar</button>
    </>
  );
}

const root = createRoot(document.getElementById('root'));
flushSync(() => root.render(<Painel />));
flushSync(() => document.getElementById('focar').click());

console.log('React', version);
console.log('elemento com foco:', document.activeElement.outerHTML);
React 19.2.8 elemento com foco: <input placeholder="Buscar pet...">

O React Compiler existe e é opcional. Ele memoriza por você em tempo de compilação, o que reduz a necessidade de useMemo e useCallback escritos à mão. É um plugin que você liga; não é mágica que já está ligada no seu projeto.

React Server Components rodam no servidor e não têm estado. Eles chegam via framework — Next.js, principalmente —, não via npm install react. Num projeto Vite você simplesmente não tem esse assunto, e não está perdendo nada aos três meses de estudo.

E as lendas que continuam circulando: componente de classe não é o padrão (hooks são desde 2019); Redux não é obrigatório; create-react-app não deve ser usado; e o Virtual DOM não é “mais rápido que o DOM” — ele é uma forma de descobrir o mínimo a mudar, o que quase sempre sai mais barato do que redesenhar tudo, e mais caro do que uma atualização manual perfeita que ninguém consegue manter.

Três projetos que valem mais do que dez tutoriais

Um projeto terminado ensina mais que cinco começados. Estes três, nesta ordem, cobrem tudo que aparece numa entrevista júnior — e todos cabem na mesma clínica veterinária.

1. A agenda com filtro na URL. Lista de consultas, filtro por status e por veterinário, contador de confirmadas. Sem back-end: um array em arquivo já serve. O critério de pronto é o link: copie o endereço com o filtro aplicado, abra numa aba anônima e a tela precisa ser a mesma.

2. O cadastro de pacientes consumindo API. Um formulário controlado que cria, uma lista que busca de uma API real e três estados desenhados na tela: carregando, erro e vazio. O estado vazio é o que separa portfólio de exercício — quase ninguém desenha, e todo revisor repara.

3. O painel com rotas e sessão. Rotas para /consultas, /consultas/:id e /pacientes, um contexto com o usuário logado e um hook customizado que você extraiu porque dois componentes repetiam a mesma lógica. Aqui você exercita, de uma vez, tudo o que a tabela de decisão do meio deste guia descreve.

Para construir essa terceira entrega com dados, filtros, formulário, persistência e testes, siga o tutorial Painel React com TypeScript e Vite. Ele transforma o roteiro acima num projeto incremental que você pode verificar.

Publique os três. Um projeto que roda numa URL vale mais que dez repositórios com README bonito.

Para mostrar que não é conversa, este é o cartão de consulta testado com node:test, sem nenhuma biblioteca de teste instalada. O Node não lê .jsx sozinho — o arquivo passou antes pelo esbuild, o mesmo transformador de JSX que o seu projeto Vite já tem — e aí node --test roda:

jsx
import test from 'node:test';
import assert from 'node:assert/strict';
import { renderToStaticMarkup } from 'react-dom/server';

function CardConsulta({ pet, tutor, horario, status }) {
  return (
    <article className={`consulta consulta--${status}`}>
      <h3>{pet}</h3>
      <p>Tutor: {tutor}</p>
      <p>Horário: {horario}</p>
    </article>
  );
}

const props = { pet: 'Amora', tutor: 'Ana Souza', horario: '09:00', status: 'confirmada' };

test('mesmas props, mesma tela', () => {
  assert.equal(
    renderToStaticMarkup(<CardConsulta {...props} />),
    renderToStaticMarkup(<CardConsulta {...props} />)
  );
});

test('o status vira classe no HTML', () => {
  const html = renderToStaticMarkup(<CardConsulta {...props} status="cancelada" />);
  assert.match(html, /consulta--cancelada/);
});

test('o nome do pet aparece no título', () => {
  const html = renderToStaticMarkup(<CardConsulta {...props} pet="Thor" />);
  assert.match(html, /<h3>Thor<\/h3>/);
});
✔ mesmas props, mesma tela (3.002209ms) ✔ o status vira classe no HTML (0.208792ms) ✔ o nome do pet aparece no título (0.122875ms) ℹ tests 3 ℹ suites 0 ℹ pass 3 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0 ℹ duration_ms 59.61625

Quando não usar React

A pergunta que quase nenhum guia faz, e que decide bem mais projeto do que a escolha entre hooks.

Lembra dos 60,12 kB comprimidos do build mínimo? Esse é o piso que o visitante baixa antes de ver o primeiro pixel — para uma página que, em HTML e CSS puros, pesaria menos de 5 kB. Numa aplicação de trabalho isso se paga em minutos. Numa landing page, não se paga nunca.

Não use React quando:

  • A página é conteúdo e o Google precisa achar. Site institucional da clínica, blog, página de contato. HTML, CSS e um pouco de JavaScript entregam mais rápido, indexam melhor e não quebram quando o script falha.
  • A interatividade cabe em cinquenta linhas. Um menu que abre, um formulário com três campos, um contador. addEventListener resolve, e você não instala nada.
  • O projeto é legado e você quer “modernizar aos poucos”. Meia migração é a pior das arquiteturas: dois modelos mentais no mesmo repositório, e ninguém sabe qual manda.
  • Você ainda não escreve JavaScript sozinho. É o caso mais frequente, e o mais caro. Sem a base, React vira um conjunto de rituais que você repete sem entender — e o primeiro erro fora do caminho feliz trava tudo.

Use React quando a tela tem muitos estados que conversam entre si e a mesma informação aparece em mais de um lugar. Foi exatamente o caso do resumo e da lista lá no começo. Quando esse problema não existe, React é custo sem contrapartida.

Por onde começar hoje

Se a base de JavaScript está firme, comece por o que é React e siga a trilha de React na ordem: componente, props, listas, useState. São quatro lições, cada uma com código executado e o erro mais comum reproduzido — os mesmos três que você viu aqui aparecem no lugar em que costumam pegar as pessoas.

E abra o projeto da agenda hoje mesmo, ainda que com um array inventado. React não se aprende lendo sobre React: aprende-se com uma tela na frente que precisa mudar quando o dado muda.

Trilha

React

Componentes, estado e as decisões que aparecem quando a tela começa a crescer.

Ver a trilha
  1. 01O que é React: componentes, estado e para que serve
  2. 02Criar projeto React com Vite: passo a passo
  3. 03JSX no React: escrever HTML dentro do JavaScript
  4. 04Props no React: passar dados de um componente para outro
  5. 05Renderização condicional no React: if, && e ternário
  6. 06Renderizar listas no React com map e a prop key
  7. 07Eventos no React: onClick, onChange e o objeto do evento
  8. 08useState no React: guardar e atualizar o estado da tela
  9. 09Atualizar estado no React sem mutar array e objeto
  10. 10Formulário controlado no React: value e onChange
  11. 11useEffect no React: efeito, dependências e limpeza
  12. 12Consumir API no React: fetch, carregando e erro
  13. 13useRef no React: acessar o DOM e guardar valor
  14. 14useMemo e useCallback: quando memorizar de verdade
  15. 15Hook customizado no React: extrair lógica repetida
  16. 16Context API no React: estado sem ficar passando props
  17. 17useReducer no React: estado com muitas transições
  18. 18children e composição no React: o fim do prop drilling
  19. 19React Router: rotas, links e parâmetros na URL
  20. 20CSS no React: módulos, Tailwind ou styled-components

react

Perguntas frequentes

Preciso saber TypeScript antes de aprender React?
Não. Componente, prop, estado e renderização podem ser aprendidos primeiro em JavaScript. Se o projeto já nasce com TypeScript, acompanhe os tipos básicos sem deixar que eles escondam o mecanismo do React.
React, Vue ou Angular: por qual começar?
Escolha um e termine um projeto antes de comparar os três. Este guia usa React porque ele também abre o caminho para React Native e tem uma base explícita de componentes e estado. Para uma decisão de carreira, confira as vagas reais da sua região e as tecnologias da empresa-alvo.
Como saber se já consigo construir um projeto React sozinho?
Você está pronto para sair do tutorial quando consegue quebrar uma tela em componentes, decidir onde o estado mora, carregar uma API, tratar espera e erro, testar uma interação e explicar as escolhas sem copiar a estrutura.
Preciso aprender Redux?
Não para começar. useState, contexto e uma biblioteca de cache de servidor cobrem os primeiros projetos. Estude Redux quando a complexidade ou uma base existente trouxer esse requisito — a sintaxe é a parte menor.
Ainda preciso reconhecer componente de classe?
Sim para manter código legado. Saiba reconhecer this.state e os métodos de ciclo de vida, mas escreva os novos exemplos com componentes de função e hooks, que são a API ensinada na documentação atual.
Dá para aprender React sem saber HTML e CSS direito?
Dá para renderizar, não dá para entregar. O JSX é HTML com outro nome, e todo componente que você escrever vai precisar de layout. CSS mal resolvido vira a maior fonte de retrabalho de quem está começando.

O código deste guia foi executado em Node 24.16.0, React 19.2.8, jsdom 30.0.1, Vite 8.2.2, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. React — Learn React — react.dev
  2. React — Rules of Hooks — react.dev
  3. React 19 — release oficial — react.dev
  4. React — Sunsetting Create React App — react.dev
  5. Vite — Getting Started — vite.dev