Você passou cinco módulos dominando JavaScript — assincronismo, APIs, Node.js, qualidade de código. Agora chegamos ao React — a biblioteca que transformou a forma como construímos interfaces web e que hoje é a habilidade front-end mais demandada do mercado.
React não é um framework completo como Angular. É uma biblioteca focada em uma coisa só: construir interfaces a partir de componentes. Simples na ideia, poderoso na prática.
O problema que o React resolve
Antes do React, interfaces dinâmicas eram construídas manipulando o DOM manualmente:
// ── Jeito antigo — jQuery / DOM puro ──────────────
let contador = 0;
function incrementar() {
contador++;
document.querySelector('#valor').textContent = contador;
document.querySelector('#btn-decrement').disabled = contador <= 0;
if (contador > 10) {
document.querySelector('#aviso').style.display = 'block';
document.querySelector('#aviso').textContent = 'Limite atingido!';
} else {
document.querySelector('#aviso').style.display = 'none';
}
}
// Para cada mudança de estado:
// 1. Calcular o que mudou
// 2. Encontrar o elemento no DOM
// 3. Atualizar manualmente
// 4. Lembrar de atualizar TUDO que depende daquele estado
// 5. Não esquecer nenhum caso especial
// ── React — descrevendo o resultado desejado ───────
function Contador() {
const [contador, setContador] = useState(0);
return (
<div>
<p>Valor: {contador}</p>
<button onClick={() => setContador(c => c + 1)}>+</button>
<button onClick={() => setContador(c => c - 1)} disabled={contador <= 0}>-</button>
{contador > 10 && <p>Limite atingido!</p>}
</div>
);
}
// React cuida de atualizar o DOM automaticamente
// Você apenas descreve como a UI deve parecer dado um estado
A diferença fundamental: com React você descreve o que deve aparecer, não como atualizar o DOM.
Instalando e criando um projeto
# Vite — a forma moderna e rápida (recomendado)
npm create vite@latest meu-app -- --template react
cd meu-app
npm install
npm run dev
# Alternativa — Create React App (mais pesado, menos atual)
npx create-react-app meu-app
cd meu-app
npm start
A estrutura gerada com Vite:
meu-app/
├── public/
│ └── vite.svg
├── src/
│ ├── assets/
│ ├── App.css
│ ├── App.jsx
│ ├── index.css
│ └── main.jsx ← ponto de entrada
├── index.html
├── package.json
└── vite.config.js
// src/main.jsx — ponto de entrada
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
JSX — JavaScript + XML
JSX é uma extensão de sintaxe que permite escrever HTML dentro do JavaScript. O Babel (ou Vite) o transforma em chamadas JavaScript:
// JSX que você escreve
const elemento = <h1 className="titulo">Olá, {nome}!</h1>;
// JavaScript que é gerado
const elemento = React.createElement(
'h1',
{ className: 'titulo' },
'Olá, ',
nome,
'!'
);
Regras importantes do JSX:
// ✅ 1. Sempre um elemento raiz (ou Fragment)
return (
<div>
<h1>Título</h1>
<p>Parágrafo</p>
</div>
);
// ✅ Fragment — sem elemento extra no DOM
return (
<>
<h1>Título</h1>
<p>Parágrafo</p>
</>
);
// ✅ 2. Expressões JavaScript entre chaves
const nome = 'Ana';
return <p>Olá, {nome}!</p>;
return <p>Resultado: {2 + 2}</p>;
return <p>{condicao ? 'Sim' : 'Não'}</p>;
// ✅ 3. className em vez de class
return <div className="container active"></div>;
// ✅ 4. htmlFor em vez de for
return <label htmlFor="email">Email:</label>;
// ✅ 5. Estilos como objeto JavaScript
return <div style={{ color: 'red', fontSize: '16px' }}></div>;
// ✅ 6. Tags auto-fechantes
return <input type="text" />;
return <img src="foto.jpg" alt="foto" />;
return <br />;
// ✅ 7. Comentários dentro do JSX
return (
<div>
{/* Isso é um comentário JSX */}
<p>Conteúdo</p>
</div>
);
Componentes — os blocos de construção
Um componente React é uma função que recebe props e retorna JSX:
// Componente simples
function Saudacao() {
return <h1>Olá, mundo!</h1>;
}
// Componente com props
function CartaoUsuario({ nome, email, avatar, ativo }) {
return (
<div className={`cartao ${ativo ? 'ativo' : 'inativo'}`}>
<img src={avatar} alt={nome} />
<h2>{nome}</h2>
<p>{email}</p>
<span>{ativo ? '✅ Ativo' : '❌ Inativo'}</span>
</div>
);
}
// Usando o componente
function App() {
return (
<div>
<CartaoUsuario
nome="Ana Paula"
email="ana@email.com"
avatar="https://api.dicebear.com/7.x/avataaars/svg?seed=Ana"
ativo={true}
/>
<CartaoUsuario
nome="Carlos Silva"
email="carlos@email.com"
avatar="https://api.dicebear.com/7.x/avataaars/svg?seed=Carlos"
ativo={false}
/>
</div>
);
}
Props — passando dados para componentes
// Props básicas
function Botao({ texto, tipo = 'button', desabilitado = false, onClick }) {
return (
<button
type={tipo}
disabled={desabilitado}
onClick={onClick}
className="btn"
>
{texto}
</button>
);
}
// Props com children — conteúdo entre as tags
function Card({ titulo, children, rodape }) {
return (
<div className="card">
<div className="card-header">
<h3>{titulo}</h3>
</div>
<div className="card-body">
{children}
</div>
{rodape && (
<div className="card-footer">
{rodape}
</div>
)}
</div>
);
}
// Usando children
function App() {
return (
<Card
titulo="Informações do usuário"
rodape={<small>Última atualização: hoje</small>}
>
<p>Nome: Ana Paula</p>
<p>Email: ana@email.com</p>
</Card>
);
}
// Spread de props — útil para passar muitas props
function EntradaTexto({ label, erro, ...inputProps }) {
return (
<div>
<label>{label}</label>
<input {...inputProps} /> {/* passa type, value, onChange, etc. */}
{erro && <span className="erro">{erro}</span>}
</div>
);
}
<EntradaTexto
label="Email"
type="email"
value={email}
onChange={e => setEmail(e.target.value)}
placeholder="seu@email.com"
erro={erroEmail}
/>
useState — gerenciando estado
import { useState } from 'react';
// Estado simples
function Contador() {
const [contador, setContador] = useState(0);
return (
<div>
<p>Contagem: {contador}</p>
<button onClick={() => setContador(contador + 1)}>+1</button>
<button onClick={() => setContador(0)}>Resetar</button>
</div>
);
}
// Estado com objeto
function Formulario() {
const [formulario, setFormulario] = useState({
nome: '',
email: '',
senha: '',
});
// Atualiza apenas o campo alterado — spread + campo dinâmico
function handleChange(e) {
const { name, value } = e.target;
setFormulario(prev => ({ ...prev, [name]: value }));
}
return (
<form>
<input name="nome" value={formulario.nome} onChange={handleChange} />
<input name="email" value={formulario.email} onChange={handleChange} />
<input name="senha" type="password" value={formulario.senha} onChange={handleChange} />
</form>
);
}
// Estado com array
function ListaTarefas() {
const [tarefas, setTarefas] = useState([
{ id: 1, texto: 'Estudar React', feita: false },
{ id: 2, texto: 'Fazer exercícios', feita: true },
]);
function adicionarTarefa(texto) {
// NUNCA mutate o estado diretamente — sempre cria novo array
setTarefas(prev => [
...prev,
{ id: Date.now(), texto, feita: false },
]);
}
function alternarTarefa(id) {
setTarefas(prev =>
prev.map(t => t.id === id ? { ...t, feita: !t.feita } : t)
);
}
function removerTarefa(id) {
setTarefas(prev => prev.filter(t => t.id !== id));
}
return (
<ul>
{tarefas.map(tarefa => (
<li key={tarefa.id}>
<input
type="checkbox"
checked={tarefa.feita}
onChange={() => alternarTarefa(tarefa.id)}
/>
<span style={{ textDecoration: tarefa.feita ? 'line-through' : 'none' }}>
{tarefa.texto}
</span>
<button onClick={() => removerTarefa(tarefa.id)}>🗑</button>
</li>
))}
</ul>
);
}
A prop key — por que é obrigatória em listas
// ❌ Sem key — React não sabe o que mudou
{itens.map(item => <Item item={item} />)}
// ❌ Key com índice — problemático quando a ordem muda
{itens.map((item, i) => <Item key={i} item={item} />)}
// ✅ Key com ID único e estável
{itens.map(item => <Item key={item.id} item={item} />)}
// A key ajuda o React a identificar quais itens
// foram adicionados, removidos ou reordenados
// Sem ela, o React re-renderiza tudo desnecessariamente
// ou piora: confunde elementos e perde estado
useEffect — efeitos colaterais
import { useState, useEffect } from 'react';
// Buscar dados ao montar o componente
function ListaUsuarios() {
const [usuarios, setUsuarios] = useState([]);
const [carregando, setCarregando] = useState(true);
const [erro, setErro] = useState(null);
useEffect(() => {
// Executa após a renderização
let cancelado = false; // evita atualizar estado após desmontagem
async function buscar() {
try {
setCarregando(true);
const res = await fetch('https://jsonplaceholder.typicode.com/users');
const dados = await res.json();
if (!cancelado) setUsuarios(dados);
} catch (e) {
if (!cancelado) setErro(e.message);
} finally {
if (!cancelado) setCarregando(false);
}
}
buscar();
// Cleanup — executado quando o componente desmonta
return () => { cancelado = true; };
}, []); // [] = executa apenas uma vez (ao montar)
if (carregando) return <p>Carregando...</p>;
if (erro) return <p>Erro: {erro}</p>;
return (
<ul>
{usuarios.map(u => <li key={u.id}>{u.name}</li>)}
</ul>
);
}
// Dependências do useEffect
function BuscaPorTermo({ termo }) {
const [resultados, setResultados] = useState([]);
useEffect(() => {
if (!termo.trim()) { setResultados([]); return; }
const controller = new AbortController();
fetch(`/api/busca?q=${termo}`, { signal: controller.signal })
.then(r => r.json())
.then(setResultados)
.catch(e => { if (e.name !== 'AbortError') console.error(e); });
return () => controller.abort(); // cancela requisição anterior
}, [termo]); // re-executa toda vez que 'termo' muda
return <ul>{resultados.map(r => <li key={r.id}>{r.nome}</li>)}</ul>;
}
// Variações do array de dependências
useEffect(() => { /* executa em toda renderização */ });
useEffect(() => { /* executa apenas ao montar */ }, []);
useEffect(() => { /* executa quando a ou b mudam */ }, [a, b]);
Renderização condicional
function StatusPedido({ status, usuario }) {
// if/return antecipado (early return)
if (!usuario) return <p>Faça login para ver o pedido.</p>;
return (
<div>
{/* Operador ternário */}
{status === 'aprovado'
? <Aprovado />
: <Pendente />
}
{/* && — renderiza apenas se verdadeiro */}
{status === 'cancelado' && (
<div className="alerta-vermelho">
Pedido cancelado. Entre em contato.
</div>
)}
{/* Múltiplas condições com switch */}
{(() => {
switch (status) {
case 'pendente': return <span>⏳ Aguardando pagamento</span>;
case 'aprovado': return <span>✅ Aprovado</span>;
case 'enviado': return <span>🚚 A caminho</span>;
case 'entregue': return <span>📦 Entregue</span>;
default: return <span>❓ Status desconhecido</span>;
}
})()}
{/* Nulo retorna nada */}
{usuario.admin && <BotaoAdmin />}
</div>
);
}
Exemplo completo — App de Tarefas
// src/App.jsx
import { useState, useEffect } from 'react';
import './App.css';
// ── Componentes ────────────────────────────────────
function Cabecalho({ total, concluidas }) {
return (
<header className="cabecalho">
<h1>📋 Minhas Tarefas</h1>
<p>{concluidas} de {total} concluídas</p>
<div className="barra-progresso">
<div
className="barra-fill"
style={{ width: total > 0 ? `${(concluidas / total) * 100}%` : '0%' }}
/>
</div>
</header>
);
}
function FormularioNovaTarefa({ aoAdicionar }) {
const [texto, setTexto] = useState('');
const [prioridade, setPrioridade] = useState('media');
function handleSubmit(e) {
e.preventDefault();
if (!texto.trim()) return;
aoAdicionar(texto.trim(), prioridade);
setTexto('');
setPrioridade('media');
}
return (
<form onSubmit={handleSubmit} className="form-nova-tarefa">
<input
type="text"
value={texto}
onChange={e => setTexto(e.target.value)}
placeholder="Nova tarefa..."
className="input-tarefa"
/>
<select
value={prioridade}
onChange={e => setPrioridade(e.target.value)}
className="select-prioridade"
>
<option value="baixa">🟢 Baixa</option>
<option value="media">🟡 Média</option>
<option value="alta">🔴 Alta</option>
</select>
<button type="submit" disabled={!texto.trim()}>
Adicionar
</button>
</form>
);
}
function ItemTarefa({ tarefa, aoAlternar, aoRemover }) {
const icones = { baixa: '🟢', media: '🟡', alta: '🔴' };
return (
<li className={`tarefa-item ${tarefa.feita ? 'feita' : ''} prioridade-${tarefa.prioridade}`}>
<input
type="checkbox"
checked={tarefa.feita}
onChange={() => aoAlternar(tarefa.id)}
/>
<span className="tarefa-texto">{tarefa.texto}</span>
<span className="tarefa-prioridade" title={tarefa.prioridade}>
{icones[tarefa.prioridade]}
</span>
<span className="tarefa-data">
{new Date(tarefa.criadaEm).toLocaleDateString('pt-BR')}
</span>
<button
onClick={() => aoRemover(tarefa.id)}
className="btn-remover"
aria-label="Remover tarefa"
>
🗑
</button>
</li>
);
}
function FiltroBotoes({ filtroAtivo, aoFiltrar, tarefas }) {
const botoes = [
{ valor: 'todas', rotulo: 'Todas', count: tarefas.length },
{ valor: 'pendentes', rotulo: 'Pendentes', count: tarefas.filter(t => !t.feita).length },
{ valor: 'concluidas', rotulo: 'Concluídas', count: tarefas.filter(t => t.feita).length },
];
return (
<div className="filtros">
{botoes.map(b => (
<button
key={b.valor}
className={`btn-filtro ${filtroAtivo === b.valor ? 'ativo' : ''}`}
onClick={() => aoFiltrar(b.valor)}
>
{b.rotulo} <span className="badge">{b.count}</span>
</button>
))}
</div>
);
}
// ── App principal ──────────────────────────────────
const CHAVE_STORAGE = 'tarefas-react';
function carregarTarefas() {
try {
const salvas = localStorage.getItem(CHAVE_STORAGE);
return salvas ? JSON.parse(salvas) : [];
} catch {
return [];
}
}
export default function App() {
const [tarefas, setTarefas] = useState(carregarTarefas);
const [filtro, setFiltro] = useState('todas');
// Persiste tarefas no localStorage quando mudam
useEffect(() => {
localStorage.setItem(CHAVE_STORAGE, JSON.stringify(tarefas));
}, [tarefas]);
function adicionarTarefa(texto, prioridade) {
setTarefas(prev => [
{
id: crypto.randomUUID(),
texto,
prioridade,
feita: false,
criadaEm: new Date().toISOString(),
},
...prev,
]);
}
function alternarTarefa(id) {
setTarefas(prev =>
prev.map(t => t.id === id ? { ...t, feita: !t.feita } : t)
);
}
function removerTarefa(id) {
setTarefas(prev => prev.filter(t => t.id !== id));
}
function limparConcluidas() {
setTarefas(prev => prev.filter(t => !t.feita));
}
// Filtragem
const tarefasFiltradas = tarefas.filter(t => {
if (filtro === 'pendentes') return !t.feita;
if (filtro === 'concluidas') return t.feita;
return true;
});
// Ordenação: alta → media → baixa → concluídas por último
const prioridadeOrdem = { alta: 0, media: 1, baixa: 2 };
const tarefasOrdenadas = [...tarefasFiltradas].sort((a, b) => {
if (a.feita !== b.feita) return a.feita ? 1 : -1;
return (prioridadeOrdem[a.prioridade] ?? 2) - (prioridadeOrdem[b.prioridade] ?? 2);
});
const concluidas = tarefas.filter(t => t.feita).length;
return (
<div className="app">
<Cabecalho total={tarefas.length} concluidas={concluidas} />
<main className="conteudo">
<FormularioNovaTarefa aoAdicionar={adicionarTarefa} />
<FiltroBotoes
filtroAtivo={filtro}
aoFiltrar={setFiltro}
tarefas={tarefas}
/>
{tarefasOrdenadas.length === 0 ? (
<p className="lista-vazia">
{filtro === 'todas'
? '🎉 Nenhuma tarefa ainda. Adicione uma!'
: `Nenhuma tarefa ${filtro === 'pendentes' ? 'pendente' : 'concluída'}.`
}
</p>
) : (
<ul className="lista-tarefas">
{tarefasOrdenadas.map(tarefa => (
<ItemTarefa
key={tarefa.id}
tarefa={tarefa}
aoAlternar={alternarTarefa}
aoRemover={removerTarefa}
/>
))}
</ul>
)}
{concluidas > 0 && (
<button
onClick={limparConcluidas}
className="btn-limpar"
>
🧹 Limpar {concluidas} concluída{concluidas !== 1 ? 's' : ''}
</button>
)}
</main>
</div>
);
}
Os conceitos fundamentais que você aprendeu
┌───────────────────────────────────────────────────────┐
│ REACT — CONCEITOS BASE │
│ │
│ Componente → função que retorna JSX │
│ Props → dados passados do pai para o filho │
│ Estado → dados que quando mudam, re-renderizam │
│ JSX → HTML + JavaScript combinados │
│ Key → identifica itens em listas │
│ useEffect → efeitos colaterais e ciclo de vida │
│ │
│ Fluxo de dados: │
│ Pai → (props) → Filho │
│ Filho → (callbacks) → Pai (lifting state up) │
└───────────────────────────────────────────────────────┘
Tarefa para você
Expanda o App de Tarefas com:
// 1. Campo de busca que filtra tarefas em tempo real
// (use debounce de 300ms com useState + useEffect)
// 2. Edição inline — ao clicar no texto, vira um input
// Salva ao pressionar Enter ou ao tirar o foco (onBlur)
// Cancela ao pressionar Escape
// 3. Drag and drop simples — reordenação manual
// Dica: atributos draggable, onDragStart, onDragOver, onDrop
// 4. Estatísticas expandidas:
// - Tempo médio de conclusão (criadaEm → data de conclusão)
// - Tarefa mais antiga ainda pendente
// - Porcentagem por prioridade
// 5. Temas — botão que alterna entre claro e escuro
// Persista a preferência no localStorage
// Use className no elemento raiz: "app tema-claro" | "app tema-escuro"
Conclusão
Neste artigo você aprendeu:
- O problema que o React resolve e por que ele mudou o front-end
- Como criar um projeto React com Vite
- JSX — suas regras e como funciona por baixo
- Componentes — funções que retornam JSX
- Props — como passar dados de pai para filho
useState— gerenciando estado local com arrays e objetos- A prop
keye por que é crucial em listas useEffect— buscando dados, limpando efeitos e dependências- Renderização condicional — ternário,
&&e early return - Como compor um App completo com múltiplos componentes
No próximo artigo vamos aprofundar os Hooks do React — useContext, useReducer, useMemo, useCallback e hooks customizados que elevam suas aplicações ao próximo nível.
📌 Próximo artigo: Aula 39 — React Hooks em profundidade
📚 Fontes e Referências
- React — Documentação oficial: https://react.dev
- React — Aprenda React (tutorial oficial): https://react.dev/learn
- Vite — Documentação: https://vitejs.dev
- React — Thinking in React: https://react.dev/learn/thinking-in-react
- React — Describing the UI: https://react.dev/learn/describing-the-ui
- JavaScript.info — React: https://javascript.info
- Road to React — Robin Wieruch: https://www.roadtoreact.com
- Learning React — Alex Banks e Eve Porcello (O'Reilly)
- roadmap.sh — React: https://roadmap.sh/react