Noções Básicas de React
23 exemplos de componentes para você começar com React -- 10 básicos e 13 intermediários.
Busque em todas as páginas da documentação
23 exemplos de componentes para você começar com React -- 10 básicos e 13 intermediários.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Tenha um novo projeto React rodando em menos de um minuto:
# Create a new Next.js app (recommended)
npx create-next-app@latest my-app --typescript --tailwind --app
cd my-app
npm run devSeu aplicativo agora está rodando em http://localhost:3000. Edite app/page.tsx para começar a construir. Se a porta 3000 já estiver em uso, o Next.js automaticamente incrementará para a próxima porta disponível (3001, 3002, etc.) -- verifique a saída do seu terminal para o URL real.
Outras opções: Você também pode usar
npm create vite@latest meu-app -- --template react-tspara uma configuração leve de Vite + React, ounpx create-react-app meu-app --template typescriptpara a abordagem clássica do CRA (não mais recomendado para novos projetos).
O componente React mais simples -- uma função que retorna JSX.
// components/hello-world.tsx
function HelloWorld() {
return <h1>Hello, World!</h1>;
}
export default HelloWorld;Para usar este componente, importe-o em outro arquivo:
// app/page.tsx
import HelloWorld from "@/components/hello-world";
export default function Home() {
return (
<main>
<HelloWorld />
</main>
);
}@/ é um atalho para a raiz do seu projeto -- então @/components/hello-world aponta para components/hello-world.tsx<HelloWorld />Relacionado: Componentes -- padrões de composição, props, children | Sintaxe de Componente de Função -- declaração vs. seta, exportação padrão vs. nomeada | JSX e TSX -- a sintaxe que os componentes retornam
Todos os exemplos neste guia usam TypeScript (.tsx files) porque é o padrão da indústria para desenvolvimento profissional de React. Eis o porquê:
Use chaves {} para incorporar expressões JavaScript dentro do JSX.
function Greeting() {
const name = "Alice";
return <p>Welcome, {name}!</p>;
}{} criam um slot de expressão -- você pode colocar qualquer expressão JavaScript dentroif ou for) diretamente dentro de chavesRelacionado: JSX e TSX -- regras de expressão, fragmentos e compilação JSX | TypeScript + Noções Básicas de React -- tipando variáveis e expressões
Mapeie um array para produzir múltiplos elementos.
function FruitList() {
const fruits = ["Apple", "Banana", "Cherry"];
return (
<ul>
{fruits.map((fruit) => (
<li key={fruit}>{fruit}</li>
))}
</ul>
);
}.map() transforma cada item do array em um elemento JSXkey única para que o React possa rastrear quais itens mudaramRelacionado: Listas e Chaves -- estratégias de chave, listas aninhadas, filtragem e armadilhas comuns
Props permitem que você passe dados de um componente pai para um filho.
function UserCard({ name, age }: { name: string; age: number }) {
return (
<div>
<h2>{name}</h2>
<p>Age: {age}</p>
</div>
);
}
// Usage: <UserCard name="Bob" age={25} />{ name, age } diretamente na lista de parâmetros para acesso limpoRelacionado: Componentes -- padrões de composição e a prop children | Tipando Props -- interfaces, props opcionais, uniões discriminadas
Mostre ou oculte conteúdo com base em uma condição.
function LoginStatus({ isLoggedIn }: { isLoggedIn: boolean }) {
return (
<div>
{isLoggedIn ? <p>Welcome back!</p> : <p>Please log in.</p>}
</div>
);
}? : é a maneira mais comum de renderizar condicionalmente em JSX&& para "mostrar ou nada": {isLoggedIn && <p>Bem-vindo!</p>}returnRelacionado: Renderização Condicional -- ternário,
&&, retorno antecipado e cláusulas de guarda em profundidade
Anexe um manipulador de eventos para responder às ações do usuário.
function ClickCounter() {
const handleClick = () => {
alert("Button clicked!");
};
return <button onClick={handleClick}>Click me</button>;
}onClick, não onclick)onClick={handleClick} não onClick={handleClick()}Relacionado: Eventos -- eventos sintéticos, delegação de eventos e padrões de manipuladores | Eventos de Mouse -- clique, duplo clique, hover | Tipando Eventos -- tipos de manipuladores de eventos
Adicione estado interativo a um componente.
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}useState retorna um par: o valor atual e uma função setteruseState(0) é o valor inicial, usado apenas na primeira renderizaçãoRelacionado: useState -- funções atualizadoras, inicialização preguiçosa e loteamento de estado | Tipando Estado -- tipando formas de estado complexas
Capture o que o usuário digita com um input controlado.
import { useState } from "react";
function NameInput() {
const [name, setName] = useState("");
return (
<div>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Your name"
/>
<p>Hello, {name || "stranger"}!</p>
</div>
);
}value={name}onChange dispara a cada tecla pressionada e atualiza o estado com o novo valorRelacionado: Formulários -- inputs controlados vs. não controlados, envio de formulário | Controlado vs. Não Controlado -- quando usar cada um | Eventos de Formulário -- onChange, onSubmit, onBlur
Use a prop especial children para envolver conteúdo.
function Card({ children }: { children: React.ReactNode }) {
return <div className="border rounded p-4 shadow">{children}</div>;
}
// Usage:
// <Card>
// <h2>Title</h2>
// <p>Some content here.</p>
// </Card>children é o que você coloca entre as tags de abertura e fechamento de um componenteReact.ReactNode aceita strings, números, elementos, arrays ou nullRelacionado: Composição -- slots, componentes compostos e padrões de layout | Componentes -- a prop children em profundidade
Adicione classes CSS ou estilos inline a elementos.
function StyledBox() {
return (
<div
className="container"
style={{ backgroundColor: "lightblue", padding: "1rem" }}
>
<p>Styled with both className and inline styles.</p>
</div>
);
}className em vez de class (já que class é uma palavra reservada em JavaScript)backgroundColor, não background-color)Relacionado: Configuração do Tailwind -- configurando o Tailwind em um projeto Next.js | Utilitários do Tailwind -- classes de espaçamento, tipografia e layout | Modo Escuro -- alternância de tema com Tailwind
Carregue dados de uma API quando o componente for montado.
import { useState, useEffect } from "react";
interface User {
id: number;
name: string;
email: string;
}
function UserProfile({ userId }: { userId: number }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
.then((res) => res.json())
.then((data) => {
setUser(data);
setLoading(false);
});
}, [userId]);
if (loading) return <p>Loading...</p>;
if (!user) return <p>User not found.</p>;
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}useEffect executa efeitos colaterais -- coisas que acontecem fora da renderização (chamadas de API, timers, subscriptions)[userId] diz ao React para reexecutar o efeito apenas quando userId mudar[] significa "executar uma vez na montagem" -- omiti-lo completamente significa "executar após cada renderização"Relacionado: useEffect -- limpeza, arrays de dependências e erros comuns | Busca Básica com SWR -- uma abordagem melhor para buscar dados com cache e revalidação | Suspense -- estados de carregamento com React Suspense
Compartilhe estado entre componentes irmãos movendo-o para o pai deles.
import { useState } from "react";
function TemperatureInput({
label,
value,
onChange,
}: {
label: string;
value: string;
onChange: (val: string) => void;
}) {
return (
<label>
{label}:{" "}
<input value={value} onChange={(e) => onChange(e.target.value)} />
</label>
);
}
function TemperatureConverter() {
const [celsius, setCelsius] = useState("");
const fahrenheit = celsius ? String((parseFloat(celsius) * 9) / 5 + 32) : "";
return (
<div>
<TemperatureInput label="Celsius" value={celsius} onChange={setCelsius} />
<p>Fahrenheit: {fahrenheit || "--"}</p>
</div>
);
}Relacionado: Padrões de Contexto -- compartilhando estado sem prop drilling | Configuração do Zustand -- quando elevar o estado não é suficiente, use um store | Context vs. Zustand -- escolhendo entre Context e Zustand
Controle a visibilidade de um componente filho a partir de um botão pai usando estado e props.
import { useState } from "react";
interface DataRowsProps {
showRows: boolean;
data: string[];
}
function DataRows({ showRows, data }: DataRowsProps) {
if (!showRows) return null;
return (
<div className="mt-4 rounded-lg border border-gray-200 bg-white shadow-sm">
{data.map((item, index) => (
<div
key={index}
className="border-b border-gray-100 px-4 py-3 last:border-0 hover:bg-gray-50"
>
{item}
</div>
))}
</div>
);
}
function Dashboard() {
const [showData, setShowData] = useState(false);
const items = ["Row 1: User Data", "Row 2: Analytics", "Row 3: Reports"];
return (
<div className="p-6">
<button
onClick={() => setShowData(!showData)}
className="rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
>
{showData ? "Hide" : "Show"} Details
</button>
<DataRows showRows={showData} data={items} />
</div>
);
}showData) e o passa para baixo como uma propshowRows -- retornar null o oculta completamentepx-4 py-2), cores (bg-blue-600 text-white), bordas (rounded-md border) e estados interativos (hover:bg-blue-700)last:border-0 remove a borda da última linha para um visual limpoRelacionado: Renderização Condicional -- retorno antecipado, guardas e padrões de visibilidade | Elevando o Estado (Exemplo 12) -- compartilhando estado entre irmãos | Composição -- construindo hierarquias de componentes flexíveis
Construa um componente flexível usando props para controlar a aparência.
function Button({
variant = "primary",
children,
onClick,
}: {
variant?: "primary" | "secondary" | "danger";
children: React.ReactNode;
onClick?: () => void;
}) {
const styles: Record<string, string> = {
primary: "bg-blue-600 text-white",
secondary: "bg-gray-200 text-gray-800",
danger: "bg-red-600 text-white",
};
return (
<button className={`px-4 py-2 rounded ${styles[variant]}`} onClick={onClick}>
{children}
</button>
);
}
// Usage:
// <Button variant="danger" onClick={handleDelete}>Delete</Button>variant = "primary") tornam as props opcionais com padrões sensatos"primary" | "secondary" | "danger" restringe a prop a opções válidasRelacionado: Componentes Compostos -- APIs de componentes multi-parte | Componente Button -- um botão de variante pronto para produção | Uniões Discriminadas -- props de variante seguras por tipo
Extraia lógica reutilizável em um hook personalizado.
import { useState } from "react";
function useToggle(initial = false) {
const [value, setValue] = useState(initial);
const toggle = () => setValue((v) => !v);
const setOn = () => setValue(true);
const setOff = () => setValue(false);
return { value, toggle, setOn, setOff };
}
// Usage in a component:
function Accordion({ title, children }: { title: string; children: React.ReactNode }) {
const { value: isOpen, toggle } = useToggle();
return (
<div>
<button onClick={toggle}>
{title} {isOpen ? "▲" : "▼"}
</button>
{isOpen && <div>{children}</div>}
</div>
);
}use e podem chamar outros hooksRelacionado: Guia de Hooks Personalizados -- regras, testes e padrões para hooks personalizados | useToggle -- o hook de alternância usado neste exemplo | Hooks Personalizados (React Hooks) -- quando e por que extrair hooks
Gerencie um formulário com várias entradas usando um único objeto de estado.
import { useState, type FormEvent } from "react";
interface FormData {
name: string;
email: string;
message: string;
}
function ContactForm() {
const [form, setForm] = useState<FormData>({ name: "", email: "", message: "" });
const handleChange = (field: keyof FormData, value: string) => {
setForm((prev) => ({ ...prev, [field]: value }));
};
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
console.log("Submitted:", form);
};
return (
<form onSubmit={handleSubmit}>
<input
placeholder="Name"
value={form.name}
onChange={(e) => handleChange("name", e.target.value)}
/>
<input
placeholder="Email"
type="email"
value={form.email}
onChange={(e) => handleChange("email", e.target.value)}
/>
<textarea
placeholder="Message"
value={form.message}
onChange={(e) => handleChange("message", e.target.value)}
/>
<button type="submit">Send</button>
</form>
);
}useState separadas{ ...prev, [field]: value } cria um novo objeto com um campo atualizadokeyof FormData garante que você só possa passar nomes de campo válidos -- erros de digitação se tornam erros de compilaçãoe.preventDefault() no envio do formulário para evitar a recarga padrão da página do navegadorRelacionado: Padrões de Formulário (Complexo) -- formulários multi-etapas e campos dinâmicos | React Hook Form + Zod -- formulários validados por esquema em escala | Checklist de Decisão de Formulário -- escolhendo a estratégia de formulário correta | Checklist de Decisão de Formulário Gherkin -- planejamento de formulário orientado a testes
Pule useEffect completamente -- busque no servidor e renderize HTML pronto.
// app/users/page.tsx
interface User {
id: number;
name: string;
email: string;
}
export default async function UsersPage() {
const res = await fetch("https://jsonplaceholder.typicode.com/users", {
next: { revalidate: 60 },
});
const users: User[] = await res.json();
return (
<ul>
{users.map((user) => (
<li key={user.id}>
{user.name} -- {user.email}
</li>
))}
</ul>
);
}async que rodam apenas no servidor -- nenhum JavaScript é enviado para o navegador para elesawait dados diretamente no corpo do componente; sem useEffect, sem flags de carregamento, sem cascata do lado do clientenext: { revalidate: 60 } armazena em cache a resposta por 60 segundos, depois busca novamente na próxima requisiçãoRelacionado: Componentes de Servidor -- quando usar componentes de servidor vs. cliente | Busca -- cache, revalidação e buscas paralelas | Componentes de Cliente -- quando você precisa de
"use client"
Lide com envios de formulário no servidor sem escrever uma rota de API.
// app/todos/page.tsx
import { revalidatePath } from "next/cache";
async function createTodo(formData: FormData) {
"use server";
const title = formData.get("title") as string;
await db.todo.create({ data: { title } });
revalidatePath("/todos");
}
export default function TodosPage() {
return (
<form action={createTodo}>
<input name="title" placeholder="New todo" required />
<button type="submit">Add</button>
</form>
);
}"use server" marca uma função como Server Action -- ela só roda no servidor, mesmo quando chamada de um formulário do cliente<form action={...}>; o React serializa os dados do formulário e invoca a ação no enviorevalidatePath("/todos") diz ao Next.js para limpar o cache da página para que o novo todo apareça imediatamentefetch, nenhum parsing de JSON -- a chamada de rede é tratada inteiramente pelo React e Next.jsRelacionado: Server Actions -- segurança, validação e tratamento de erros | Form Actions -- suporte a actions de formulário do React 19 | Revalidação --
revalidatePathvs.revalidateTag
Rastreie o estado pendente e as respostas do servidor de uma action de formulário.
"use client";
import { useActionState } from "react";
import { subscribe } from "./actions";
interface State {
message: string;
ok: boolean;
}
const initialState: State = { message: "", ok: false };
export function SubscribeForm() {
const [state, formAction, isPending] = useActionState(subscribe, initialState);
return (
<form action={formAction}>
<input name="email" type="email" required />
<button type="submit" disabled={isPending}>
{isPending ? "Subscribing..." : "Subscribe"}
</button>
{state.message && (
<p className={state.ok ? "text-green-600" : "text-red-600"}>{state.message}</p>
)}
</form>
);
}E o arquivo de action do servidor correspondente:
// app/subscribe/actions.ts
"use server";
interface State {
message: string;
ok: boolean;
}
export async function subscribe(_prev: State, formData: FormData): Promise<State> {
const email = formData.get("email");
if (typeof email !== "string" || !email.includes("@")) {
return { message: "Please enter a valid email address.", ok: false };
}
try {
await db.subscriber.create({ data: { email } });
return { message: `Subscribed ${email}!`, ok: true };
} catch (err) {
if (isUniqueConstraintError(err)) {
return { message: "You're already subscribed.", ok: false };
}
return { message: "Something went wrong. Try again.", ok: false };
}
}useActionState envolve uma Server Action e retorna [state, action, isPending] em uma única chamadaisPending muda para true durante o envio e de volta para false quando a action é resolvida -- perfeita para desabilitar botões e mostrar spinners(prevState, formData) e o que quer que ela retorne se torna o novo state"use server" no topo de actions.ts marca cada exportação como uma Server Action -- elas rodam apenas no servidor, então chamadas de banco de dados e segredos permanecem segurosFormData no servidor antes de confiá-los -- o cliente pode enviar qualquer coisa, então typeof email !== "string" e verificações de forma pertencem à própria action"use client"), mas a action subjacente ainda roda no servidorRelacionado: useActionState -- forma de retorno, tratamento de erros e padrões de validação | Form Actions -- o contrato de actions de formulário do React 19 | Server Actions -- validando FormData no servidor
Mostre o resultado instantaneamente enquanto o servidor se atualiza.
"use client";
import { useOptimistic, useState } from "react";
import { addLike } from "./actions";
export function LikeButton({ postId, initialLikes }: { postId: number; initialLikes: number }) {
const [likes, setLikes] = useState(initialLikes);
const [optimisticLikes, addOptimisticLike] = useOptimistic(
likes,
(current) => current + 1,
);
async function handleClick() {
addOptimisticLike(null);
const next = await addLike(postId);
setLikes(next);
}
return (
<button onClick={handleClick}>
♥ {optimisticLikes}
</button>
);
}useOptimistic fornece um estado temporário que atualiza instantaneamente e depois reverte se a action falhar(current) => current + 1 descreve como aplicar a atualização otimista sobre o estado realsetLikes(next)), o React reconcilia e o valor otimista é substituídoRelacionado: useOptimistic -- padrões de reducer, múltiplas atualizações pendentes e recuperação de erros | Server Actions -- combinando UI otimista com mutações
Transmita partes da página à medida que seus dados ficam prontos.
// app/dashboard/page.tsx
import { Suspense } from "react";
import { RecentOrders } from "./recent-orders";
import { SalesChart } from "./sales-chart";
export default function DashboardPage() {
return (
<div className="grid grid-cols-2 gap-4">
<Suspense fallback={<p>Loading orders...</p>}>
<RecentOrders />
</Suspense>
<Suspense fallback={<p>Loading chart...</p>}>
<SalesChart />
</Suspense>
</div>
);
}async que busca seus próprios dados -- o lento não bloqueia o rápido<Suspense fallback={...}> diz ao React: "mostre este placeholder até que o filho seja resolvido"Promise.all -- a árvore apenas renderiza à medida que cada peça fica prontaRelacionado: Suspense -- limites, suspense aninhado e tratamento de erros | Streaming -- como o Next.js transmite HTML | Suspense + Streaming (Performance) -- medindo e otimizando performance de streaming | UI de Carregamento e Erro --
loading.tsxem nível de rota
Envie atualizações ao vivo do servidor para o navegador através de uma única conexão HTTP.
// app/api/ticker/route.ts
export async function GET() {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
for (let i = 0; i < 10; i++) {
const data = JSON.stringify({ tick: i, time: Date.now() });
controller.enqueue(encoder.encode(`data: ${data}\n\n`));
await new Promise((r) => setTimeout(r, 1000));
}
controller.close();
},
});
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
},
});
}// app/ticker/page.tsx
"use client";
import { useEffect, useState } from "react";
interface Tick {
tick: number;
time: number;
}
export default function TickerPage() {
const [ticks, setTicks] = useState<Tick[]>([]);
useEffect(() => {
const source = new EventSource("/api/ticker");
source.onmessage = (e) => {
setTicks((prev) => [...prev, JSON.parse(e.data)]);
};
return () => source.close();
}, []);
return (
<ul>
{ticks.map((t) => (
<li key={t.tick}>Tick {t.tick} at {new Date(t.time).toLocaleTimeString()}</li>
))}
</ul>
);
}ReadableStream com Content-Type: text/event-stream -- o formato do protocolo SSE são apenas linhas como data: <payload>\n\nEventSource embutido do navegador reconecta automaticamente em caso de quedas de rede e entrega cada linha data: para onmessageuseEffect que chame source.close() -- caso contrário, a conexão vaza na desmontagem ou navegaçãoRelacionado: Streaming -- streaming de HTML vs. streaming de dados | Server Actions -- quando mutar vs. assinar | useEffect -- funções de limpeza e padrões de subscription
Passe dados pela árvore sem prop drilling.
"use client";
import { createContext, useContext, useState, type ReactNode } from "react";
type Theme = "light" | "dark";
interface ThemeContextValue {
theme: Theme;
toggle: () => void;
}
const ThemeContext = createContext<ThemeContextValue | null>(null);
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<Theme>("light");
const toggle = () => setTheme((t) => (t === "light" ? "dark" : "light"));
return (
<ThemeContext.Provider value={{ theme, toggle }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error("useTheme must be used inside <ThemeProvider>");
return ctx;
}
// Usage in any nested component:
function ThemeToggle() {
const { theme, toggle } = useTheme();
return <button onClick={toggle}>Current theme: {theme}</button>;
}createContext cria um contêiner para valores compartilhados; <ThemeContext.Provider value={...}> envolve a parte da árvore que pode lê-louseContext(ThemeContext) para ler o valor -- não há necessidade de passar props através de cada componente intermediáriouseTheme) e lançar um erro quando o provider está faltando fornece um erro claro em vez de um null silenciosoRelacionado: useContext -- padrões de provider, performance e erros comuns | Padrões de Contexto -- dividindo estado vs. dispatch, providers compostos | Context vs. Zustand -- quando usar um store em vez disso
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥