kb.erickguedes.com
React: Construindo Interfaces Modernas

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.