Ecossistema React
Aula 8 de 8
React Router
ash npm install react-router-dom
Setup Básico
` sx import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
function App() { return ( <BrowserRouter> <nav> <Link to="/">Home</Link> <Link to="/sobre">Sobre</Link> <Link to="/contato">Contato</Link> </nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/sobre" element={<Sobre />} />
<Route path="/contato" element={<Contato />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
); } `
useParams e useNavigate
` sx import { useParams, useNavigate } from "react-router-dom";
function Produto() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate();
return ( <div> <h1>Produto {id}</h1> <button onClick={() => navigate("/")}>Voltar</button> <button onClick={() => navigate("/produto/" + (Number(id) + 1))}> Próximo </button> </div> ); } `
Outlet e Layouts
` sx import { Outlet, NavLink } from "react-router-dom";
function Layout() { return ( <div> <header> <nav> <NavLink to="/" end>Home</NavLink> <NavLink to="/dashboard">Dashboard</NavLink> </nav> </header>
<main>
<Outlet /> {/* Renderiza o conteúdo da rota aninhada */}
</main>
</div>
); }
// Rotas function Rotas() { return ( <Routes> <Route element={<Layout />}> <Route path="/" element={<Home />} /> <Route path="/dashboard" element={<Dashboard />} /> <Route path="/dashboard/settings" element={<Settings />} /> </Route> </Routes> ); } `
Loaders (React Router v6.4+)
` sx import { createBrowserRouter, RouterProvider } from "react-router-dom";
interface Usuario { id: number; nome: string; }
async function loaderPerfil({ params }: { params: { id: string } }) { const res = await fetch(/api/usuarios/\); if (!res.ok) throw new Response("Not Found", { status: 404 }); return (await res.json()) as Usuario; }
function Perfil() { const usuario = useLoaderData() as Usuario; return <h1>{usuario.nome}</h1>; }
const router = createBrowserRouter([ { path: "/perfil/:id", element: <Perfil />, loader: loaderPerfil, errorElement: <ErrorBoundary />, }, ]);
function App() { return <RouterProvider router={router} />; } `
State Management
Zustand
ash npm install zustand
` ypescript import { create } from "zustand";
interface AuthState { token: string | null; usuario: { nome: string } | null; login: (email: string, senha: string) => Promise<void>; logout: () => void; }
const useAuthStore = create<AuthState>((set) => ({ token: null, usuario: null,
login: async (email, senha) => { const res = await fetch("/api/login", { method: "POST", body: JSON.stringify({ email, senha }), }); const data = await res.json(); set({ token: data.token, usuario: data.usuario }); },
logout: () => set({ token: null, usuario: null }), }));
// Uso em qualquer componente function Perfil() { const { usuario, logout } = useAuthStore();
return ( <div> <p>Bem-vindo, {usuario?.nome}!</p> <button onClick={logout}>Sair</button> </div> ); } `
Redux Toolkit
ash npm install @reduxjs/toolkit react-redux
` ypescript import { createSlice, configureStore } from "@reduxjs/toolkit"; import { Provider, useSelector, useDispatch } from "react-redux";
// Slice const contadorSlice = createSlice({ name: "contador", initialState: { valor: 0 }, reducers: { incrementar: (state) => { state.valor += 1; }, decrementar: (state) => { state.valor -= 1; }, somar: (state, action) => { state.valor += action.payload; }, }, });
export const { incrementar, decrementar, somar } = contadorSlice.actions;
// Store const store = configureStore({ reducer: { contador: contadorSlice.reducer }, });
type RootState = ReturnType<typeof store.getState>; type AppDispatch = typeof store.dispatch;
// Provider function App() { return ( <Provider store={store}> <Contador /> </Provider> ); }
// Componente function Contador() { const valor = useSelector((state: RootState) => state.contador.valor); const dispatch = useDispatch<AppDispatch>();
return ( <div> <p>Valor: {valor}</p> <button onClick={() => dispatch(incrementar())}>+</button> <button onClick={() => dispatch(decrementar())}>-</button> <button onClick={() => dispatch(somar(10))}>+10</button> </div> ); } `
Jotai
ash npm install jotai
` sx import { atom, useAtom } from "jotai";
// Átomos: estado atômico e reativo const contadorAtom = atom(0); const dobradoAtom = atom((get) => get(contadorAtom) * 2);
function Contador() { const [contador, setContador] = useAtom(contadorAtom); const [dobrado] = useAtom(dobradoAtom);
return ( <div> <p>Contador: {contador}</p> <p>Dobrado: {dobrado}</p> <button onClick={() => setContador((c) => c + 1)}>+</button> </div> ); } `
Server Components (Conceitos)
React Server Components (RSC) executam no servidor e enviam apenas HTML/resultado ao cliente:
Server Component (server) → Renderiza HTML + dados ↓ Client Component (browser) → Hidrata e gerencia interatividade
sx // Server Component (arquivo .server.tsx ou padrão Next.js App Router) // Executa no servidor, pode acessar banco diretamente async function ListaProdutos() { const produtos = await db.produto.findMany(); // acesso direto ao banco return ( <ul> {produtos.map((p) => ( <li key={p.id}>{p.nome} - R$ {p.preco}</li> ))} </ul> ); }
Next.js Introdução
ash npx create-next-app@latest meu-app --typescript
` ypescript // app/page.tsx — App Router (Server Component por padrão) export default async function Home() { const res = await fetch("https://api.exemplo.com/dados"); const dados = await res.json();
return ( <main> <h1>{dados.titulo}</h1> <p>{dados.descricao}</p> </main> ); } `
ypescript // app/produtos/[id]/page.tsx — Rota dinâmica export default async function ProdutoPage({ params, }: { params: { id: string }; }) { return <h1>Produto: {params.id}</h1>; }
Client Component (Next.js)
` sx "use client"; // marca como Client Component
import { useState } from "react";
export default function Contador() { const [count, setCount] = useState(0); return <button onClick={() => setCount(count + 1)}>{count}</button>; } `
React 19 — Novas Features
use() Hook
` sx import { use } from "react";
// use(): lê uma Promise ou Context diretamente, integrado com Suspense function Comentarios({ promise }: { promise: Promise<string[]> }) { const comentarios = use(promise); return ( <ul> {comentarios.map((c, i) => <li key={i}>{c}</li>)} </ul> ); } `
Actions (form action)
` sx "use client";
import { useActionState } from "react";
async function enviarForm(prevState: unknown, formData: FormData) { const nome = formData.get("nome"); // validação e envio return { sucesso: true, mensagem: \Olá, !\ }; }
function Formulario() { const [state, action, pendente] = useActionState(enviarForm, null);
return ( <form action={action}> <input name="nome" required /> <button type="submit" disabled={pendente}> {pendente ? "Enviando..." : "Enviar"} </button> {state?.mensagem && <p>{state.mensagem}</p>} </form> ); } `
form Actions (React 19)
` sx // Sem useActionState — ação direta no form
<form action={async (formData) => { "use server"; // executa no servidor await salvarDados(formData); }}> <input name="email" /> <button type="submit">Salvar</button> </form> `Lab: Aplicação Completa
ash npm install react-router-dom zustand
` sx // stores/carrinho.ts import { create } from "zustand";
interface ItemCarrinho { id: number; nome: string; preco: number; quantidade: number; }
interface CarrinhoStore { items: ItemCarrinho[]; adicionar: (item: Omit<ItemCarrinho, "quantidade">) => void; remover: (id: number) => void; total: () => number; }
export const useCarrinho = create<CarrinhoStore>((set, get) => ({ items: [], adicionar: (item) => set((state) => { const existente = state.items.find((i) => i.id === item.id); if (existente) { return { items: state.items.map((i) => i.id === item.id ? { ...i, quantidade: i.quantidade + 1 } : i ), }; } return { items: [...state.items, { ...item, quantidade: 1 }] }; }), remover: (id) => set((state) => ({ items: state.items.filter((i) => i.id !== id), })), total: () => get().items.reduce((acc, i) => acc + i.preco * i.quantidade, 0), })); `
` sx // pages/Produtos.tsx import { useCarrinho } from "../stores/carrinho"; import { Link } from "react-router-dom";
const produtos = [ { id: 1, nome: "Camiseta", preco: 49.90 }, { id: 2, nome: "Calça", preco: 129.90 }, { id: 3, nome: "Tênis", preco: 299.90 }, ];
export default function Produtos() { const adicionar = useCarrinho((s) => s.adicionar);
return ( <div> <h1>Produtos</h1> <Link to="/carrinho">Ver Carrinho</Link> <div> {produtos.map((p) => ( <div key={p.id}> <h3>{p.nome}</h3> <p>R$ {p.preco.toFixed(2)}</p> <button onClick={() => adicionar(p)}>Adicionar</button> </div> ))} </div> </div> ); } `
` sx // pages/Carrinho.tsx import { useCarrinho } from "../stores/carrinho";
export default function Carrinho() { const { items, remover, total } = useCarrinho();
if (items.length === 0) return <p>Carrinho vazio</p>;
return ( <div> <h1>Carrinho</h1> {items.map((item) => ( <div key={item.id}> <span>{item.nome} x{item.quantidade}</span> <span>R$ {(item.preco * item.quantidade).toFixed(2)}</span> <button onClick={() => remover(item.id)}>Remover</button> </div> ))} <h3>Total: R$ {total().toFixed(2)}</h3> </div> ); } `
ash npm run dev
React 19 traz use() e Actions para simplificar dados assíncronos e formulários. Zustand é a escolha moderna para state management global — simples, type-safe e sem boilerplate. Next.js é o framework recomendado para produção com SSR, RSC e otimizações embutidas.