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