Cheatsheet React
Biblioteca JavaScript para interfaces
React
Componentes
Componente funcional
function Ola() {
return <h1>Olá Mundo!</h1>;
}
export default Ola;Um componente funcional é uma função JavaScript que retorna JSX. É a forma padrão de criar componentes no React moderno.
Composição de componentes
function App() {
return (
<div>
<Header />
<Main />
<Footer />
</div>
);
}Compor é montar a interface juntando componentes menores dentro de outros. Cada componente fica simples e reutilizável.
Spread de props
const props = { nome: "Ana", idade: 30 };
// Passar todas as props de uma vez:
<Perfil {...props} />
// Equivale a:
<Perfil nome="Ana" idade={30} />O spread (...) espalha um objeto como props individuais. Útil para repassar props sem as listar uma a uma.
Renderizar com &&
function Notificacoes({ count }) {
return (
<div>
<h1>Mensagens</h1>
{count > 0 && (
<span className="badge">{count}</span>
)}
</div>
);
}O operador && renderiza o lado direito só se a condição for verdadeira. Mais curto que um ternário quando não há alternativa.
Componente com arrow function
const Ola = () => <h1>Olá Mundo!</h1>; export default Ola;
Versão mais curta com arrow function. Muito usada para componentes simples de uma linha.
Atributos em JSX
<input
type="text"
placeholder="Nome"
disabled={true}
tabIndex={1}
data-id="campo-nome"
/>Os atributos seguem o padrão HTML, mas com nomes em camelCase (tabIndex) e valores dinâmicos entre { }.
Componente como variável
function App({ tipo }) {
const Componente = tipo === "admin"
? PainelAdmin
: PainelUser;
return <Componente />;
}Podes guardar um componente numa variável (com maiúscula) e renderizá-lo dinamicamente. O nome deve começar por letra maiúscula.
Componente puro (memo)
const Item = React.memo(function Item({ nome }) {
console.log("renderizou:", nome);
return <li>{nome}</li>;
});
// Só re-renderiza se "nome" mudar
// Comparação rasa (shallow) das propsUm componente puro (React.memo) só re-renderiza quando as props mudam. Evita renders desnecessários em listas grandes.
JSX básico
function Cartao() {
return (
<div className="cartao">
<h2>Título</h2>
<p>Conteúdo aqui</p>
<img src="/foto.jpg" alt="Foto" />
</div>
);
}JSX parece HTML, mas usa className em vez de class. Um componente deve retornar um único elemento raiz.
Renderizar no DOM
import { createRoot } from "react-dom/client";
import App from "./App";
const root = createRoot(
document.getElementById("root")
);
root.render(<App />);O createRoot liga o React a um elemento HTML (normalmente uma div#root). O render desenha o componente principal na página.
Early return
function Perfil({ user }) {
if (!user) return <p>Faz login</p>;
if (user.bloqueado) return <p>Conta suspensa</p>;
return (
<div>
<h1>{user.nome}</h1>
<p>{user.email}</p>
</div>
);
}O early return trata casos especiais no início com return antecipado. O código principal fica limpo, sem ternários aninhados.
Fragment
function Linha() {
return (
<>
<td>Nome</td>
<td>Valor</td>
</>
);
}
// Equivalente:
import { Fragment } from "react";
return <Fragment>...</Fragment>;O Fragment (<>...</>) agrupa elementos sem criar um nó extra no DOM. Essencial quando um componente precisa retornar múltiplos irmãos.
Expressões em JSX
function Perfil({ nome, idade }) {
return (
<div>
<p>{nome}</p>
<p>{idade + 1} anos</p>
<p>{"texto".toUpperCase()}</p>
</div>
);
}Usa { } para inserir qualquer expressão JavaScript dentro do JSX: variáveis, cálculos ou chamadas de funções.
Comentários em JSX
function Cartao() {
return (
<div>
{/* Isto é um comentário em JSX */}
<h2>Título</h2>
{/* <p>Texto removido</p> */}
</div>
);
}Dentro do JSX, os comentários usam {/* ... */}. Fora do JSX (no JavaScript), usa // normalmente.
Props com TypeScript
interface Props {
nome: string;
idade?: number; // opcional
onSalvar: (id: number) => void;
}
function Perfil({ nome, idade = 0, onSalvar }: Props) {
return <p>{nome}, {idade}</p>;
}Com TypeScript, defines uma interface para as props. O compilador avisa se faltar uma prop obrigatória ou o tipo estiver errado.
Children prop
function Card({ titulo, children }) {
return (
<div className="card">
<h2>{titulo}</h2>
<div className="corpo">{children}</div>
</div>
);
}
// Uso - o conteúdo entre as tags vira children:
<Card titulo="Perfil">
<p>Nome: Ana</p>
<button>Editar</button>
</Card>A prop children recebe tudo o que é colocado entre as tags de abertura e fecho do componente. Ideal para wrappers como cards, modais e layouts.
Props e Estado
Props
function Saudacao({ nome, idade }) {
return <p>Olá {nome}, {idade} anos</p>;
}
<Saudacao nome="Ana" idade={30} />Props são os dados passados de um componente pai para um filho. Funcionam como argumentos de uma função.
Estado com array
const [itens, setItens] = useState([]);
// Adicionar:
setItens([...itens, novoItem]);
// Remover:
setItens(itens.filter(i => i.id !== id));
// Atualizar:
setItens(itens.map(i =>
i.id === id ? { ...i, nome: "novo" } : i
));Arrays de estado são atualizados de forma imutável: cria um novo array com filter, map ou spread, em vez de alterar o original.
Atualização assíncrona
const [count, setCount] = useState(0); // ❌ Usa o valor antigo: setCount(count + 1); setCount(count + 1); // +1, não +2! // ✅ Forma funcional (correta): setCount(prev => prev + 1); setCount(prev => prev + 1); // +2 ✓
O estado é assíncrono: dentro do mesmo evento, o valor não muda imediatamente. Usa a forma funcional prev => para atualizações encadeadas.
Controlled vs Uncontrolled
// Controlled: React controla o valor
const [valor, setValor] = useState("");
<input value={valor} onChange={e => setValor(e.target.value)} />
// Uncontrolled: DOM controla (com ref)
const ref = useRef();
<input ref={ref} defaultValue="texto" />
// Ler: ref.current.valueNo controlled, o React é a fonte de verdade (value + onChange). No uncontrolled, o DOM guarda o valor e lês com ref. Prefere controlled.
Estado vs Props
function ContadorPai() {
const [total, setTotal] = useState(0); // ESTADO (dono)
return (
<>
<Mostrador valor={total} /> {/* PROP (só leitura) */}
<button onClick={() => setTotal(total + 1)}>
+1
</button>
</>
);
}
function Mostrador({ valor }) {
// valor é uma PROP - não pode ser modificada aqui
return <p>Total: {valor}</p>;
}Estado (useState) é dados que o componente controla e pode mudar. Props são dados recebidos do pai, só de leitura. O dono do estado passa-o como prop.
Props são só de leitura
function Botao({ texto }) {
// texto = "novo"; ❌ Erro!
return <button>{texto}</button>;
}Uma prop nunca deve ser modificada dentro do componente que a recebe. Para dados que mudam, usa o estado.
Lifting state up
function Pai() {
const [valor, setValor] = useState("");
return (
<Filho valor={valor} onChange={setValor} />
);
}
function Filho({ valor, onChange }) {
return (
<input
value={valor}
onChange={e => onChange(e.target.value)}
/>
);
}Quando dois componentes precisam do mesmo estado, sobe-o para o pai comum e passa-o para baixo via props.
Estado derivado
const [itens, setItens] = useState([]); // ✅ Calcula durante o render: const total = itens.reduce((s, i) => s + i.preco, 0); const ativos = itens.filter(i => i.ativo); // ❌ Não guardes em estado: // const [total, setTotal] = useState(0);
Se um valor pode ser calculado a partir de props ou estado existente, não o guardes em estado separado. Calcula-o diretamente no render.
Callback props
// Pai passa função como prop:
function Pai() {
const [itens, setItens] = useState([]);
return (
<Filho onAdicionar={(item) =>
setItens(prev => [...prev, item])
} />
);
}
// Filho chama a callback:
function Filho({ onAdicionar }) {
return (
<button onClick={() => onAdicionar("novo")}>
Adicionar
</button>
);
}O padrão callback props permite ao filho comunicar com o pai. O filho chama a função e o pai decide o que fazer com os dados.
useState
import { useState } from "react";
function Contador() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Clique: {count}
</button>
);
}O useState cria uma variável de estado e a função para a atualizar. Quando o estado muda, o componente re-renderiza.
Destructuring de props
// Sem destructuring:
function Perfil(props) {
return <p>{props.nome}, {props.idade}</p>;
}
// Com destructuring (mais limpo):
function Perfil({ nome, idade }) {
return <p>{nome}, {idade}</p>;
}O destructuring extrai as props diretamente nos parâmetros. Torna o código mais curto e legível, sobretudo com muitas props.
Imutabilidade
const [user, setUser] = useState({ nome: "Ana", idade: 30 });
// ❌ Muta o objeto (não re-renderiza!)
user.nome = "Rui";
setUser(user);
// ✅ Cria novo objeto
setUser({ ...user, nome: "Rui" });
// ✅ Array: novo array
setItens([...itens, novo]);O React só re-renderiza se a referência mudar. Muta o objeto e passa a mesma referência = nada acontece. Cria sempre um novo com spread.
Default props
function Botao({ cor = "azul", tamanho = "md", children }) {
return (
<button className={`btn btn-${cor} btn-${tamanho}`}>
{children}
</button>
);
}
// Uso:
<Botao>Padrão</Botao> // azul, md
<Botao cor="vermelho">Erro</Botao> // vermelho, mdUse destructuring com defaults para definir valores padrão nas props. O componente funciona mesmo sem passar todas as props.
Estado com objeto
const [form, setForm] = useState({
nome: "",
email: ""
});
setForm(prev => ({
...prev,
nome: "Ana"
}));Para objetos, usa o spread (...) para copiar os campos existentes e atualizar apenas alguns. Nunca mutes o objeto diretamente.
Prop drilling
// O tema passa por vários níveis sem ser usado:
function App() {
const [tema, setTema] = useState("dark");
return <Painel tema={tema} />;
}
function Painel({ tema }) {
return <Botao tema={tema} />; // só repassa
}
function Botao({ tema }) {
return <button className={tema} />;
}Prop drilling é passar props por vários componentes que só as repassam. Para muitos níveis, usa o Context (ver Padrões Avançados).
Estado com função (lazy init)
// ❌ Calcula em todos os renders:
const [dados, setDados] = useState(
JSON.parse(localStorage.getItem("x"))
);
// ✅ Calcula só uma vez:
const [dados, setDados] = useState(
() => JSON.parse(localStorage.getItem("x"))
);Passa uma função ao useState quando o valor inicial é caro de calcular. O React só a executa no primeiro render (lazy initialization).
useReducer (estado complexo)
import { useReducer } from "react";
const inicial = { count: 0, step: 1 };
function reducer(estado, acao) {
switch (acao.type) {
case "incrementar":
return { ...estado, count: estado.count + estado.step };
case "reset":
return inicial;
default:
return estado;
}
}
function Contador() {
const [estado, dispatch] = useReducer(reducer, inicial);
return (
<button onClick={() => dispatch({ type: "incrementar" })}>
{estado.count}
</button>
);
}useReducer é uma alternativa ao useState para estado com lógica complexa. Um reducer centraliza as transições e o dispatch envia ações.
Hooks
useEffect
import { useEffect } from "react";
useEffect(() => {
document.title = `Cliques: ${count}`;
return () => {
// cleanup (opcional)
};
}, [count]);O useEffect executa código após o render. O array de dependências controla quando volta a correr.
useRef
import { useRef } from "react";
function Foco() {
const inputRef = useRef(null);
return (
<>
<input ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>
Focar
</button>
</>
);
}O useRef guarda um valor persistente que não causa re-render. Muito usado para aceder diretamente a elementos do DOM.
useDeferredValue
import { useDeferredValue } from "react";
function Pesquisa({ query }) {
const deferred = useDeferredValue(query);
const resultados = useMemo(
() => lista.filter(i => i.includes(deferred)),
[deferred]
);
return <Lista itens={resultados} />;
}O useDeferredValue adia a atualização de um valor, mantendo a UI responsiva. A lista atualiza logo, os resultados vêm depois.
useEffect com AbortController
useEffect(() => {
const controller = new AbortController();
fetch("/api/dados", {
signal: controller.signal,
})
.then(r => r.json())
.then(setDados)
.catch(err => {
if (err.name !== "AbortError") throw err;
});
return () => controller.abort();
}, [id]);O AbortController cancela pedidos pendentes quando o componente desmonta ou a dependência muda. Evita memory leaks e race conditions.
useSyncExternalStore
import { useSyncExternalStore } from "react";
// Subscrever uma store externa (ex.: localStorage):
function useLocalStorage(key) {
return useSyncExternalStore(
(callback) => {
window.addEventListener("storage", callback);
return () => window.removeEventListener("storage", callback);
},
() => localStorage.getItem(key)
);
}
// Uso:
function Perfil() {
const nome = useLocalStorage("nome");
return <p>Olá, {nome}</p>;
}useSyncExternalStore subscreve fontes de dados externas ao React (stores, browser APIs). Garante leituras consistentes sem tearing.
useEffect: buscar dados
const [dados, setDados] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch("/api/produtos")
.then(r => r.json())
.then(data => {
setDados(data);
setLoading(false);
});
}, []);Com o array de dependências vazio [], o efeito corre apenas uma vez, quando o componente monta. Ideal para carregar dados.
useMemo
import { useMemo } from "react";
const total = useMemo(
() => itens.reduce((s, i) => s + i.preco, 0),
[itens]
);O useMemo memoriza o resultado de um cálculo pesado e só o recalcula quando as dependências mudam. Evita trabalho repetido.
useImperativeHandle
const Modal = forwardRef((props, ref) => {
const [aberto, setAberto] = useState(false);
useImperativeHandle(ref, () => ({
abrir: () => setAberto(true),
fechar: () => setAberto(false),
}));
return aberto ? <div>Modal</div> : null;
});
// No pai: modalRef.current.abrir();O useImperativeHandle expõe métodos personalizados do filho para o pai via ref. O pai chama ref.current.abrir() diretamente.
Custom hook: useFetch
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch(url)
.then(r => r.json())
.then(d => { setData(d); setLoading(false); })
.catch(e => { setError(e); setLoading(false); });
}, [url]);
return { data, loading, error };
}
// Uso: const { data, loading } = useFetch("/api/x");Um useFetch encapsula o padrão de data fetching. Devolve data, loading e error. Reutilizável em qualquer componente.
Regras dos Hooks
// CERTO - no topo do componente:
function App() {
const [a, setA] = useState(0);
const [b, setB] = useState(0);
useEffect(() => { }, []);
// ...
}
// ERRADO - dentro de condição:
function App() {
if (logado) {
const [x, setX] = useState(0); // ❌ ERRO
}
for (...) {
useEffect(() => { }); // ❌ ERRO
}
}Hooks devem ser chamados sempre na mesma ordem, no topo do componente. Nunca dentro de condições, loops ou funções aninhadas. O React depende da ordem de chamada.
useEffect: dependências
// Corre em todos os renders:
useEffect(() => { ... });
// Corre só ao montar:
useEffect(() => { ... }, []);
// Corre quando "id" muda:
useEffect(() => { ... }, [id]);O array de dependências decide quando o efeito executa: sem array corre sempre, vazio corre uma vez, com valores corre quando esses valores mudam.
useCallback
import { useCallback } from "react";
const handleClick = useCallback(
(id) => setSelecionado(id),
[]
);O useCallback memoriza uma função para que a referência seja estável entre renders. Útil ao passar funções para filhos otimizados.
useEffect vs useLayoutEffect
// useEffect: após o browser pintar (assíncrono)
useEffect(() => {
console.log("depois do paint");
}, []);
// useLayoutEffect: antes do paint (síncrono)
useLayoutEffect(() => {
// Medir DOM antes de o utilizador ver
const rect = ref.current.getBoundingClientRect();
setPosicao(rect.top);
}, []);O useLayoutEffect corre antes do browser pintar. Usa-o para medições do DOM que precisam de estar prontas antes do render visual.
useId
import { useId } from "react";
function Campo({ label }) {
const id = useId();
return (
<>
<label htmlFor={id}>{label}</label>
<input id={id} />
</>
);
}
// Cada instância gera um ID único: ":r1:", ":r2:"...useId() gera IDs únicos e estáveis para acessibilidade (label + input). Nunca usar Math.random() para IDs de acessibilidade.
Hook use() (React 19)
import { use, Suspense } from "react";
// O use() lê uma promise dentro do render:
function Perfil({ userPromise }) {
const user = use(userPromise); // suspende até resolver
return <p>{user.nome}</p>;
}
// Envolve com Suspense:
<Suspense fallback={<Spinner />}>
<Perfil userPromise={fetchUser()} />
</Suspense>O use() (React 19) lê o valor de uma promise diretamente no render, suspendendo o componente até ela resolver. Ao contrário do useContext, pode ser chamado dentro de condições.
useEffect: cleanup
useEffect(() => {
const timer = setInterval(() => {
setSegundos(s => s + 1);
}, 1000);
return () => clearInterval(timer);
}, []);A função retornada faz a limpeza antes do componente desmontar ou do efeito repetir. Essencial para timers, listeners e subscrições.
Custom Hook
function useLocalStorage(key, inicial) {
const [valor, setValor] = useState(
() => JSON.parse(localStorage.getItem(key)) ?? inicial
);
useEffect(() => {
localStorage.setItem(key, JSON.stringify(valor));
}, [key, valor]);
return [valor, setValor];
}Um custom hook é uma função que começa por use e reutiliza lógica com estado entre vários componentes.
useDebugValue
function useOnline() {
const [online, setOnline] = useState(navigator.onLine);
useDebugValue(online ? "Online" : "Offline");
return online;
}
// Visível no React DevTools:
// useOnline: "Online"O useDebugValue mostra um valor personalizado no React DevTools para custom hooks. Facilita a depuração sem console.log.
useTransition
import { useTransition, useState } from "react";
function Pesquisa() {
const [isPending, startTransition] = useTransition();
const [resultados, setResultados] = useState([]);
const pesquisar = (texto) => {
startTransition(() => {
setResultados(filtrar(texto)); // não bloqueia o input
});
};
return (
<>
<input onChange={e => pesquisar(e.target.value)} />
{isPending ? <Spinner /> : <Lista itens={resultados} />}
</>
);
}useTransition marca atualizações como não-urgentes. O input mantém-se responsivo enquanto a lista pesada atualiza em background.
useOptimistic (React 19)
import { useOptimistic } from "react";
function Likes({ total, gostar }) {
const [otimista, setOtimista] = useOptimistic(total);
async function handleClick() {
setOtimista(otimista + 1); // mostra já, antes da resposta
await gostar(); // o estado real vem do servidor
}
return (
<button onClick={handleClick}>
Gosto: {otimista}
</button>
);
}O useOptimistic (React 19) mostra de imediato o resultado esperado, antes de o servidor responder. Se o pedido falhar, o valor volta automaticamente ao estado real.
Eventos e Formulários
Event handlers
function Botao() {
const handleClick = (e) => {
e.preventDefault();
console.log("clicado!");
};
return (
<button onClick={handleClick}>
Clicar
</button>
);
}Os eventos usam nomes em camelCase (onClick, onChange, onSubmit) e recebem uma função, não uma string.
Checkbox
const [aceite, setAceite] = useState(false);
<label>
<input
type="checkbox"
checked={aceite}
onChange={e => setAceite(e.target.checked)}
/>
Aceito os termos
</label>No checkbox usa checked (não value) e lê e.target.checked para saber se ficou marcado ou desmarcado.
Upload de ficheiros
function Upload() {
const [ficheiro, setFicheiro] = useState(null);
const handleSubmit = async (e) => {
e.preventDefault();
const formData = new FormData();
formData.append("ficheiro", ficheiro);
await fetch("/api/upload", {
method: "POST",
body: formData,
});
};
return (
<form onSubmit={handleSubmit}>
<input type="file"
onChange={e => setFicheiro(e.target.files[0])} />
<button>Enviar</button>
</form>
);
}Para upload, usa FormData e o input type="file". Não definas Content-Type — o browser define o multipart automaticamente.
Vários inputs com name
function Form() {
const [dados, setDados] = useState({
nome: "", email: "", cidade: ""
});
const handleChange = (e) => {
const { name, value } = e.target;
setDados(prev => ({ ...prev, [name]: value }));
};
return (
<>
<input name="nome" value={dados.nome} onChange={handleChange} />
<input name="email" value={dados.email} onChange={handleChange} />
<input name="cidade" value={dados.cidade} onChange={handleChange} />
</>
);
}Um único handler trata todos os inputs usando o atributo name. A computed property [name] atualiza o campo certo dinamicamente.
Controlled input
function Form() {
const [nome, setNome] = useState("");
return (
<input
value={nome}
onChange={(e) => setNome(e.target.value)}
placeholder="Nome"
/>
);
}Num input controlado, o valor vem do estado e cada tecla atualiza-o. O React é a única fonte de verdade.
Radio buttons
const [opcao, setOpcao] = useState("a");
<label>
<input
type="radio"
value="a"
checked={opcao === "a"}
onChange={e => setOpcao(e.target.value)}
/>
Opção A
</label>Cada radio tem um value e fica checked quando corresponde ao estado. O onChange guarda o valor escolhido.
Validação em tempo real
const [email, setEmail] = useState("");
const [erro, setErro] = useState("");
const handleChange = (e) => {
const valor = e.target.value;
setEmail(valor);
if (valor && !valor.includes("@")) {
setErro("Email inválido");
} else {
setErro("");
}
};
<input value={email} onChange={handleChange} />
{erro && <span className="erro">{erro}</span>}A validação em tempo real verifica a cada tecla. Mostra o erro imediatamente e limpa-o quando o input fica válido.
Select e textarea controlados
function Form() {
const [pais, setPais] = useState("pt");
const [msg, setMsg] = useState("");
return (
<>
<select value={pais} onChange={e => setPais(e.target.value)}>
<option value="pt">Portugal</option>
<option value="br">Brasil</option>
<option value="ao">Angola</option>
</select>
<textarea
value={msg}
onChange={e => setMsg(e.target.value)}
rows={4}
/>
</>
);
}select e textarea também são controlados com value + onChange. No select, o value corresponde ao atributo value da option selecionada.
Formulário com submit
function FormLogin() {
const [email, setEmail] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
console.log({ email });
};
return (
<form onSubmit={handleSubmit}>
<input
value={email}
onChange={e => setEmail(e.target.value)}
/>
<button type="submit">Entrar</button>
</form>
);
}Usa onSubmit no form e e.preventDefault() para evitar o recarregamento da página. Lê os valores do estado.
Eventos de teclado
<input
onKeyDown={(e) => {
if (e.key === "Enter") {
pesquisar();
}
}}
/>
// e.key: a tecla premida
// e.code: o código físico da teclaOs eventos onKeyDown/onKeyUp detetam teclas. Usa e.key para saber qual foi premida (ex.: "Enter").
Formulário com reset
const inicial = { nome: "", email: "" };
const [form, setForm] = useState(inicial);
const handleSubmit = async (e) => {
e.preventDefault();
await fetch("/api", {
method: "POST",
body: JSON.stringify(form),
});
setForm(inicial); // limpar
};
<form onSubmit={handleSubmit}>
<input value={form.nome}
onChange={e => setForm({ ...form, nome: e.target.value })} />
<button>Enviar</button>
</form>Para limpar o formulário após o submit, basta repor o estado para o objeto inicial. Os inputs controlados atualizam automaticamente.
Actions: useActionState (React 19)
import { useActionState } from "react";
import { useFormStatus } from "react-dom";
async function criar(estadoAnterior, formData) {
await fetch("/api/tarefas", {
method: "POST",
body: formData,
});
return { ok: true };
}
function Botao() {
const { pending } = useFormStatus();
return (
<button disabled={pending}>
{pending ? "A gravar..." : "Gravar"}
</button>
);
}
function Form() {
const [estado, action] = useActionState(criar, null);
return (
<form action={action}>
<input name="titulo" />
<Botao />
</form>
);
}Com Actions (React 19), o form usa action={fn} e envia sem handlers manuais. O useActionState guarda o resultado e o useFormStatus expõe o estado pending — sem useState nem setLoading.
Passar argumentos
<button onClick={() => eliminar(item.id)}>
Remover
</button>
<li onMouseEnter={() => setHover(id)}>
{nome}
</li>Para passar parâmetros ao handler, envolve-o numa arrow function. Assim controlas exatamente o que é enviado.
Debounce em pesquisa
const [texto, setTexto] = useState("");
const [resultados, setResultados] = useState([]);
useEffect(() => {
const timer = setTimeout(() => {
fetch(`/api?q=${texto}`)
.then(r => r.json())
.then(setResultados);
}, 300);
return () => clearTimeout(timer);
}, [texto]);O debounce espera o utilizador parar de escrever antes de pesquisar. O cleanup cancela o timer anterior a cada tecla.
Submeter com Enter
function Chat() {
const [msg, setMsg] = useState("");
const enviar = () => {
if (!msg.trim()) return;
console.log("Enviado:", msg);
setMsg("");
};
return (
<input
value={msg}
onChange={e => setMsg(e.target.value)}
onKeyDown={e => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
enviar();
}
}}
/>
);
}Use onKeyDown para detetar a tecla Enter. Verifique e.key === "Enter" e previna o default para não inserir nova linha.
Listas e Condicionais
Renderizar lista
function ListaProdutos({ produtos }) {
return (
<ul>
{produtos.map(p => (
<li key={p.id}>{p.nome}</li>
))}
</ul>
);
}Usa map para transformar cada item do array num elemento JSX. O React renderiza a lista completa.
Lista vazia
{itens.length === 0 ? (
<p>Nenhum item encontrado.</p>
) : (
<ul>
{itens.map(i => <li key={i.id}>{i.nome}</li>)}
</ul>
)}Trata sempre o estado vazio com uma mensagem amiga, em vez de mostrar uma lista em branco.
Ordenação de lista
const [ordem, setOrdem] = useState("asc");
const ordenados = useMemo(() => {
return [...itens].sort((a, b) =>
ordem === "asc"
? a.nome.localeCompare(b.nome)
: b.nome.localeCompare(a.nome)
);
}, [itens, ordem]);
<button onClick={() =>
setOrdem(o => o === "asc" ? "desc" : "asc")
}>
Ordenar: {ordem}
</button>Cria uma cópia com [...itens] antes de ordenar (o sort muta o original). O useMemo evita reordenar a cada render.
Infinite scroll
const [pagina, setPagina] = useState(1);
const [itens, setItens] = useState([]);
useEffect(() => {
fetch(`/api/itens?page=${pagina}`)
.then(r => r.json())
.then(novos => setItens(prev => [...prev, ...novos]));
}, [pagina]);
// Detetar fim da página:
const onScroll = () => {
if (window.innerHeight + window.scrollY >=
document.body.offsetHeight - 200) {
setPagina(p => p + 1);
}
};
window.addEventListener("scroll", onScroll);O infinite scroll carrega mais dados quando o utilizador chega ao fim. Deteta a posição do scroll e incrementa a página.
A importância do key
// ✅ Correto: id único e estável
{itens.map(i => <li key={i.id}>{i.nome}</li>)}
// ❌ Evitar: usar o índice
{itens.map((i, idx) => <li key={idx}>{i.nome}</li>)}O key identifica cada item de forma única. Usar o índice pode causar bugs quando a lista é reordenada ou filtrada.
Variável de conteúdo
let conteudo;
if (loading) {
conteudo = <Spinner />;
} else if (erro) {
conteudo = <p>Erro ao carregar</p>;
} else {
conteudo = <Dados dados={dados} />;
}
return <div>{conteudo}</div>;Para condições com vários ramos, atribui o JSX a uma variável com if/else e renderiza-a no fim. Mais legível que ternários aninhados.
Paginação simples
const [pagina, setPagina] = useState(1);
const porPagina = 10;
const inicio = (pagina - 1) * porPagina;
const visiveis = itens.slice(inicio, inicio + porPagina);
const totalPaginas = Math.ceil(itens.length / porPagina);
return (
<>
{visiveis.map(i => <Cartao key={i.id} {...i} />)}
<button disabled={pagina === 1}
onClick={() => setPagina(p => p - 1)}>Anterior</button>
<span>{pagina} / {totalPaginas}</span>
<button disabled={pagina === totalPaginas}
onClick={() => setPagina(p => p + 1)}>Seguinte</button>
</>
);A paginação mostra só uma fatia dos dados com slice. Os botões anterior/seguinte controlam a página atual.
Lista agrupada
const grupos = itens.reduce((acc, item) => {
(acc[item.categoria] ??= []).push(item);
return acc;
}, {});
return (
<div>
{Object.entries(grupos).map(([cat, lista]) => (
<section key={cat}>
<h3>{cat}</h3>
<ul>
{lista.map(i => <li key={i.id}>{i.nome}</li>)}
</ul>
</section>
))}
</div>
);Use reduce() para agrupar itens por categoria e depois Object.entries() para iterar os grupos com os seus itens.
Renderização condicional
// Ternário:
{logado ? <Painel /> : <Login />}
// && (curto-circuito):
{erros.length > 0 && (
<div className="erro">{erros[0]}</div>
)}O ternário escolhe entre duas opções. O operador && mostra algo só quando a condição é verdadeira.
Listas aninhadas
{categorias.map(cat => (
<div key={cat.id}>
<h3>{cat.nome}</h3>
<ul>
{cat.itens.map(item => (
<li key={item.id}>{item.nome}</li>
))}
</ul>
</div>
))}Para listas dentro de listas, faz map aninhado. Cada nível precisa do seu próprio key único.
Filtro em tempo real
const [busca, setBusca] = useState("");
const filtrados = useMemo(() => {
if (!busca) return itens;
const termo = busca.toLowerCase();
return itens.filter(i =>
i.nome.toLowerCase().includes(termo)
);
}, [itens, busca]);
return (
<>
<input value={busca}
onChange={e => setBusca(e.target.value)}
placeholder="Pesquisar..." />
{filtrados.map(i => <Cartao key={i.id} {...i} />)}
</>
);O filtro em tempo real usa useMemo para só recalcular quando a busca ou os itens mudam. A lista atualiza a cada tecla.
Condicionais com objeto
const icones = {
sucesso: "✅",
erro: "❌",
aviso: "⚠️",
info: "ℹ️",
};
function Alerta({ tipo, msg }) {
return (
<div className={`alerta-${tipo}`}>
<span>{icones[tipo] ?? "❓"}</span>
{msg}
</div>
);
}
// Uso:
<Alerta tipo="sucesso" msg="Guardado!" />Um objeto de lookup substitui longas cadeias de if/else ou switch. O operador ?? dá um fallback para chaves inexistentes.
Filtrar e transformar
const ativos = produtos
.filter(p => p.ativo)
.map(p => (
<Cartao key={p.id} produto={p} />
));
return <div>{ativos}</div>;Combina filter (para selecionar) com map (para renderizar). Primeiro filtras, depois transformas em componentes.
Ternário vs &&
// Ternário: dois resultados possíveis
{admin ? <PainelAdmin /> : <PainelUser />}
// &&: mostra só se verdadeiro
{erros.length > 0 && <Alerta erros={erros} />}
// Cuidado: 0 && <X /> renderiza "0"!
{count > 0 && <p>{count} itens</p>}Usa ternário quando há duas opções e && para mostrar ou esconder. Atenção: 0 && ... mostra o zero — verifica com > 0.
Virtualização (react-window)
import { FixedSizeList } from "react-window";
<FixedSizeList
height={400}
itemCount={10000}
itemSize={35}
width="100%"
>
{({ index, style }) => (
<div style={style}>Item {index}</div>
)}
</FixedSizeList>A virtualização renderiza só os itens visíveis no viewport. Com 10.000 itens, só ~20 estão no DOM. Essencial para listas enormes.
Estilos e CSS
Inline styles
const estilo = {
color: "white",
backgroundColor: "blue",
padding: "1rem",
borderRadius: "8px"
};
<div style={estilo}>Olá</div>
<p style={{ fontSize: 14 }}>Texto</p>O atributo style recebe um objeto JavaScript com propriedades em camelCase. Bom para estilos dinâmicos simples.
Variáveis CSS (tema)
/* tema.css */
:root {
--cor-primaria: #3b82f6;
--espaco: 1rem;
}
/* componente */
.cartao {
color: var(--cor-primaria);
padding: var(--espaco);
}As variáveis CSS (--nome) centralizam cores e espaçamentos. Mudar o tema é tão simples como trocar o valor na raiz.
CSS-in-JS (styled-components)
import styled from "styled-components";
const Botao = styled.button`
background: ${props => props.$primario ? "#0070f3" : "#eee"};
color: ${props => props.$primario ? "#fff" : "#333"};
padding: 8px 16px;
border-radius: 6px;
border: none;
`;
// Uso:
<Botao $primario>Salvar</Botao>
<Botao>Cancelar</Botao>styled-components permite escrever CSS real dentro do JS. As props dinâmicas geram estilos condicionais sem classes.
CSS Modules
/* Botao.module.css */
.botao { padding: 8px 16px; }
.primario { background: blue; }import styles from "./Botao.module.css";
<button className={styles.botao}>Clicar</button>Os CSS Modules dão um nome único a cada classe, evitando conflitos globais. O estilo fica com escopo local ao componente.
Animações CSS
/* styles.css */
@keyframes aparecer {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.cartao {
animation: aparecer 0.3s ease-out;
}// No componente: <div className="cartao">Conteúdo</div>
As animações CSS (@keyframes) dão transições suaves sem JavaScript. Aplica a classe ao elemento e o browser anima automaticamente.
Tailwind CSS
function Card({ titulo, ativo }) {
return (
<div className={
"rounded-lg p-4 shadow " +
(ativo ? "bg-blue-500 text-white" : "bg-white text-gray-800")
}>
<h2 className="text-xl font-bold">{titulo}</h2>
<p className="mt-2 text-sm opacity-75">Conteúdo do card</p>
</div>
);
}
// Com template literals:
<div className={`p-4 ${ativo ? "bg-blue-500" : "bg-white"}`}>Tailwind usa classes utilitárias diretamente no JSX. Combine com ternários ou template literals para estilos condicionais.
className condicional
<div className={`cartao ${ativo ? "ativo" : ""}`}>
<button className={`btn ${
variante === "primary"
? "btn-primary"
: "btn-secondary"
}`}>Combina classes fixas e dinâmicas com template literals. A classe é aplicada conforme a condição.
clsx / classnames
import clsx from "clsx";
<button className={clsx(
"btn",
{
"btn-primary": variante === "primary",
"btn-lg": grande,
"disabled": !ativo,
}
)}>
Clicar
</button>A biblioteca clsx junta classes condicionalmente de forma limpa. Evita template literals complexos com múltiplas condições.
Estilos dinâmicos
function Barra({ progresso }) {
return (
<div
style={{
width: `${progresso}%`,
backgroundColor: progresso > 80 ? "green" : "orange"
}}
/>
);
}Calcula estilos a partir do estado ou props. O objeto de estilo é recriado a cada render com os valores atuais.
Dark mode toggle
const [tema, setTema] = useState("light");
useEffect(() => {
document.documentElement.dataset.tema = tema;
}, [tema]);
<button onClick={() =>
setTema(t => t === "light" ? "dark" : "light")
}>
{tema === "light" ? "🌙" : "☀️"}
</button>
/* CSS: */
[data-tema="dark"] { --fundo: #1a1a2e; --texto: #eee; }
[data-tema="light"] { --fundo: #fff; --texto: #333; }O dark mode alterna um atributo no html e o CSS usa variáveis para trocar as cores. O estado guarda a preferência.
Padrões Avançados
Context API
import { createContext, useContext } from "react";
const ThemeCtx = createContext("light");
// Provider:
<ThemeCtx.Provider value="dark">
<App />
</ThemeCtx.Provider>
// Consumer:
function Botao() {
const tema = useContext(ThemeCtx);
return <button className={tema}>...</button>;
}O Context partilha dados com toda a árvore de componentes sem passar props por todos os níveis (prop drilling).
forwardRef
import { forwardRef } from "react";
const Input = forwardRef((props, ref) => {
return <input ref={ref} {...props} />;
});
// No pai:
const ref = useRef();
<Input ref={ref} />
ref.current.focus();O forwardRef permite passar uma ref para um elemento dentro de um componente filho. Útil para focar inputs ou medir elementos.
Error Boundary
import { Component } from "react";
class ErrorBoundary extends Component {
state = { erro: null };
static getDerivedStateFromError(erro) {
return { erro };
}
render() {
if (this.state.erro) {
return <h1>Algo correu mal: {this.state.erro.message}</h1>;
}
return this.props.children;
}
}
// Uso:
<ErrorBoundary>
<ComponenteQuePodeFalhar />
</ErrorBoundary>Um Error Boundary captura erros de renderização nos filhos e mostra um fallback. Ainda requer class component (único caso).
Dividir Context (performance)
// ❌ Um context gigante re-renderiza todos:
<Ctx.Provider value={{ user, tema, dispatch }}>
// ✅ Contexts separados re-renderizam só quem usa:
<UserCtx.Provider value={user}>
<TemaCtx.Provider value={tema}>
<App />
</TemaCtx.Provider>
</UserCtx.Provider>
// Ou memoriza o value:
<Ctx.Provider value={useMemo(
() => ({ user, dispatch }), [user]
)}>Mudar um context re-renderiza todos os consumidores. Divide em contexts pequenos ou memoriza o value com useMemo para que cada componente só re-renderize quando os dados que usa mudarem.
Estado global simples
const AppCtx = createContext();
function AppProvider({ children }) {
const [state, dispatch] = useReducer(
reducer, estadoInicial
);
return (
<AppCtx.Provider value={{ state, dispatch }}>
{children}
</AppCtx.Provider>
);
}
// Em qualquer componente:
const { state, dispatch } = useContext(AppCtx);Junta useContext + useReducer para criar um estado global simples, sem bibliotecas externas.
Compound Components
function Tabs({ children }) {
const [ativa, setAtiva] = useState(0);
return (
<div>
{children.map((child, i) =>
cloneElement(child, {
ativa: i === ativa,
onClick: () => setAtiva(i),
})
)}
</div>
);
}
<Tabs>
<Tab label="Perfil">...</Tab>
<Tab label="Definições">...</Tab>
</Tabs>No padrão Compound Components, componentes pai e filho partilham estado implicitamente. A API fica declarativa e flexível.
Portal (createPortal)
import { createPortal } from "react-dom";
function Modal({ aberto, children, onFechar }) {
if (!aberto) return null;
return createPortal(
<div className="overlay" onClick={onFechar}>
<div className="modal" onClick={e => e.stopPropagation()}>
{children}
</div>
</div>,
document.body // renderiza fora da árvore
);
}createPortal renderiza filhos num nó DOM diferente (ex.: body). Ideal para modais, tooltips e dropdowns que precisam escapar do overflow.
Lazy loading
import { lazy, Suspense } from "react";
const Painel = lazy(() => import("./Painel"));
function App() {
return (
<Suspense fallback={<Spinner />}>
<Painel />
</Suspense>
);
}O lazy carrega o componente só quando é preciso (code-splitting). O Suspense mostra um fallback durante o carregamento.
Slot pattern (children)
function Layout({ header, sidebar, children }) {
return (
<div className="layout">
<header>{header}</header>
<aside>{sidebar}</aside>
<main>{children}</main>
</div>
);
}
<Layout
header={<Nav />}
sidebar={<Menu />}
>
<Pagina />
</Layout>O padrão slot usa props para injectar JSX em zonas específicas do layout. Mais explícito que children para múltiplas áreas.
Higher-Order Component (HOC)
function comAuth(Componente) {
return function ComponenteProtegido(props) {
const { user } = useAuth();
if (!user) {
return <p>Faça login para continuar.</p>;
}
return <Componente {...props} user={user} />;
};
}
// Uso:
const PainelProtegido = comAuth(Painel);
// No router:
<PainelProtegido />Um HOC é uma função que recebe um componente e devolve um novo com comportamento extra. Útil para autenticação, logging ou injeção de props.
Render props
function Dados({ url, children }) {
const [dados, setDados] = useState(null);
useEffect(() => {
fetch(url).then(r => r.json()).then(setDados);
}, [url]);
return children(dados);
}
<Dados url="/api/user">
{(user) => <p>{user?.nome}</p>}
</Dados>No padrão render props, o children é uma função que recebe dados do componente. Inverte o controlo de forma flexível.
Suspense para dados
// Com React Query / SWR:
function Produtos() {
const { data, isLoading } = useQuery(
["produtos"],
() => fetch("/api/produtos").then(r => r.json())
);
if (isLoading) return <Spinner />;
return <Lista itens={data} />;
}
// Ou com Suspense nativo (React 19+):
<Suspense fallback={<Spinner />}>
<Produtos />
</Suspense>Bibliotecas como React Query ou SWR gerem cache, loading e erros. O Suspense mostra o fallback enquanto os dados carregam.
ref como prop (React 19)
// React 19: ref é uma prop normal (sem forwardRef):
function Input({ ref, ...props }) {
return <input ref={ref} {...props} />;
}
// No pai:
const ref = useRef();
<Input ref={ref} />
ref.current.focus();
// Em React 18 era preciso:
// const Input = forwardRef((props, ref) => ...);No React 19, a ref passa a ser uma prop normal dos componentes de função — o forwardRef deixa de ser necessário. Basta receber { ref } e aplicá-la ao elemento.
Ecossistema e CLI
Criar projeto (Vite)
npm create vite@latest minha-app -- --template react cd minha-app npm install npm run dev
O Vite cria um projeto React com hot reload instantâneo. É a forma recomendada de começar um projeto novo.
Estrutura de pastas
src/ ├── components/ # Componentes reutilizáveis ├── pages/ # Páginas por rota ├── hooks/ # Custom hooks ├── services/ # Chamadas à API ├── utils/ # Funções utilitárias └── App.jsx
Uma estrutura organizada por responsabilidade facilita encontrar e manter o código à medida que o projeto cresce.
Deploy (build)
# Build de produção npm run build # Preview local do build npm run preview # O output fica em dist/ # Faz deploy para: Vercel, Netlify, Cloudflare Pages
O build gera ficheiros otimizados (minificados, com hash) na pasta dist/. Qualquer servidor estático ou CDN os serve.
TanStack Router
import { createRouter, RouterProvider } from "@tanstack/react-router";
const router = createRouter({
routes: [
{ path: "/", component: Home },
{ path: "/users/$id", component: UserDetail },
],
});
function App() {
return <RouterProvider router={router} />;
}
// No componente:
const { id } = useParams({ from: "/users/$id" });O TanStack Router é type-safe por natureza: rotas, params e search são inferidos automaticamente pelo TypeScript.
React Router
import { BrowserRouter, Routes, Route, Link }
from "react-router-dom";
<BrowserRouter>
<nav>
<Link to="/">Início</Link>
<Link to="/sobre">Sobre</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/sobre" element={<Sobre />} />
<Route path="/user/:id" element={<Perfil />} />
</Routes>
</BrowserRouter>O React Router gere a navegação numa SPA. Define rotas com Route e navega com Link sem recarregar a página.
Boas práticas
• Componentes pequenos e focados • key estável em listas (não usar index) • Estado o mais abaixo possível • useMemo/useCallback só quando necessário • Separar lógica em custom hooks • TypeScript para props tipadas
Convenções que mantêm o código React limpo, rápido e fácil de manter a longo prazo.
Next.js (básico)
npx create-next-app@latest minha-app
// app/page.jsx (Server Component por defeito)
export default function Home() {
return <h1>Olá Next.js</h1>;
}
// app/api/dados/route.js (API route)
export async function GET() {
return Response.json({ ok: true });
}
// Routing por pastas: app/sobre/page.jsx = /sobreO Next.js é um framework React com routing por ficheiros, SSR e API routes. Cada pasta em app/ é uma rota automaticamente.
Vitest (testes)
import { render, screen } from "@testing-library/react";
import { expect, test } from "vitest";
import Contador from "./Contador";
test("mostra o contador", () => {
render(<Contador />);
expect(screen.getByText("0")).toBeInTheDocument();
});
test("incrementa ao clicar", async () => {
render(<Contador />);
await userEvent.click(screen.getByRole("button"));
expect(screen.getByText("1")).toBeInTheDocument();
});
// Executar: npx vitestVitest é o test runner padrão do Vite. Com a Testing Library, teste o comportamento (clicar, ver texto) em vez da implementação.
useParams e useNavigate
import { useParams, useNavigate }
from "react-router-dom";
function Perfil() {
const { id } = useParams();
const navigate = useNavigate();
const voltar = () => navigate(-1);
const irHome = () => navigate("/");
}O useParams lê parâmetros da URL (:id). O useNavigate permite navegar programaticamente.
Variáveis de ambiente
// Ficheiro .env VITE_API_URL=https://api.exemplo.com // No código (Vite): const url = import.meta.env.VITE_API_URL; // Create React App: const url = process.env.REACT_APP_API_URL;
As variáveis de ambiente guardam configurações (URLs, chaves públicas) fora do código. No Vite, começam por VITE_.
Zustand (estado global)
import { create } from "zustand";
const useStore = create((set) => ({
count: 0,
incrementar: () => set(s => ({ count: s.count + 1 })),
reset: () => set({ count: 0 }),
}));
// Em qualquer componente:
function Contador() {
const { count, incrementar } = useStore();
return <button onClick={incrementar}>{count}</button>;
}O Zustand é uma alternativa minimalista ao Redux para estado global. Sem providers, sem boilerplate — um hook simples.
Server Components
// Server Component (padrão no Next.js App Router):
// Corre no servidor e pode aceder à BD diretamente:
async function Produtos() {
const produtos = await db.query(
"SELECT * FROM produtos"
);
return (
<ul>
{produtos.map(p => <li key={p.id}>{p.nome}</li>)}
</ul>
);
}
// Client Component (precisa de hooks/eventos):
"use client";
function Contador() {
const [n, setN] = useState(0);
return <button onClick={() => setN(n + 1)}>{n}</button>;
}Server Components correm no servidor e enviam HTML pronto — ideais para buscar dados sem enviar JavaScript extra. Usa "use client" só nos componentes que precisam de hooks ou eventos.
Fetch com async/await
function Produtos() {
const [dados, setDados] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function buscar() {
const res = await fetch("/api/produtos");
const json = await res.json();
setDados(json);
setLoading(false);
}
buscar();
}, []);
if (loading) return <p>A carregar...</p>;
return <Lista itens={dados} />;
}Padrão completo de data fetching: estado de loading, pedido assíncrono no useEffect e renderização condicional.
Axios (alternativa ao fetch)
import axios from "axios";
useEffect(() => {
axios.get("/api/produtos")
.then(res => setDados(res.data))
.catch(err => setErro(err.message));
}, []);
// POST:
axios.post("/api/user", { nome: "Ana" });O Axios é uma biblioteca de pedidos HTTP. Dá o JSON já tratado em res.data e tem uma API mais simples que o fetch.
React Hook Form
import { useForm } from "react-hook-form";
function Login() {
const { register, handleSubmit, formState } = useForm();
const onSubmit = (dados) => console.log(dados);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("email", { required: true })} />
{formState.errors.email && <span>Obrigatório</span>}
<button>Entrar</button>
</form>
);
}O React Hook Form gere formulários com performance (sem re-render a cada tecla). O register liga o input e a validação declarativa.
StrictMode
import { StrictMode } from "react";
root.render(
<StrictMode>
<App />
</StrictMode>
);
// Em desenvolvimento, o StrictMode:
// - renderiza tudo 2x (apanha renders impuros)
// - executa efeitos 2x (apanha efeitos sem cleanup)
// Em produção não tem qualquer efeito.O StrictMode é uma ferramenta de desenvolvimento que deteta problemas: renderiza tudo duas vezes e executa efeitos a mais, expondo renders impuros e efeitos sem cleanup. Não afeta a produção.