💻 Exemplos: Capítulo 07: Estados Complexos com useReducer e Redutores Puros
Especialização Frontend com React & TypeScript • Demonstrações Práticas Executáveis
🎯 Contexto e Objetivo Prático
Gerenciamento de fluxos transacionais complexos com useReducer, Discriminated Unions para Actions e cálculo puro de estado.
💻 Código Fonte React 19 (TSX)
// ShoppingCartReducer.tsx
import React, { useReducer } from "react";
export interface CartItem {
id: string;
name: string;
unitPrice: number;
quantity: number;
}
export interface CartState {
items: CartItem[];
couponCode?: string;
discountPercentage: number;
}
// Discriminated Union para Actions Estritas
export type CartAction =
| { type: "ADD_ITEM"; payload: Omit<CartItem, "quantity"> }
| { type: "REMOVE_ITEM"; payload: { id: string } }
| { type: "UPDATE_QUANTITY"; payload: { id: string; quantity: number } }
| { type: "APPLY_COUPON"; payload: { code: string; discount: number } }
| { type: "CLEAR_CART" };
export function cartReducer(state: CartState, action: CartAction): CartState {
switch (action.type) {
case "ADD_ITEM": {
const existing = state.items.find(i => i.id === action.payload.id);
if (existing) {
return {
...state,
items: state.items.map(i =>
i.id === action.payload.id ? { ...i, quantity: i.quantity + 1 } : i
)
};
}
return {
...state,
items: [...state.items, { ...action.payload, quantity: 1 }]
};
}
case "REMOVE_ITEM":
return {
...state,
items: state.items.filter(i => i.id !== action.payload.id)
};
case "UPDATE_QUANTITY":
return {
...state,
items: state.items.map(i =>
i.id === action.payload.id ? { ...i, quantity: Math.max(1, action.payload.quantity) } : i
)
};
case "APPLY_COUPON":
return {
...state,
couponCode: action.payload.code,
discountPercentage: action.payload.discount
};
case "CLEAR_CART":
return { items: [], discountPercentage: 0 };
default:
return state;
}
}
export function CartWidget() {
const [state, dispatch] = useReducer(cartReducer, { items: [], discountPercentage: 0 });
const totalRaw = state.items.reduce((acc, i) => acc + i.unitPrice * i.quantity, 0);
const totalFinal = totalRaw * (1 - state.discountPercentage / 100);
return (
<div className="p-4 border rounded-xl max-w-sm bg-white shadow-sm">
<h3 className="font-bold text-slate-800 mb-2">Carrinho Corporativo ({state.items.length})</h3>
<div className="space-y-1 mb-4">
{state.items.map(item => (
<div key={item.id} className="flex justify-between text-sm">
<span>{item.name} (x{item.quantity})</span>
<span>R$ {(item.unitPrice * item.quantity).toFixed(2)}</span>
</div>
))}
</div>
<div className="border-t pt-2 text-sm font-bold flex justify-between">
<span>Total a Pagar:</span>
<span className="text-emerald-600">R$ {totalFinal.toFixed(2)}</span>
</div>
<button
onClick={() => dispatch({ type: "ADD_ITEM", payload: { id: "p1", name: "Licença TS Pro", unitPrice: 199.0 } })}
className="w-full mt-3 bg-indigo-600 text-white text-xs py-2 rounded font-semibold"
>
+ Adicionar Licença TS Pro
</button>
</div>
);
}
🖥️ Saída de Renderização Visual e Comportamento
[Carrinho de Compras Reducer]
Licença TS Pro (x1) R$ 199,00
Desconto (CUPOM-10): -10%
Total a Pagar: R$ 179,10